Filebrowser mit Bildervorschau auf Basis C#/ASP.NET

Wenn die Cam (hier: IN-5907 HD) zahlreiche Alarmbilder mit FTP auf einen Webserver hochlädt, ist sehr schnell ein Recherche-Tool erforderlich, mit dem man im Webbrowser aus einer Vorschau der aufgenommenen Bilder zur Großansicht wechseln und nach Datum filtern kann. Die nachfolgende Lösung wurde auf Basis C#/ASP.NET 4.x entwickelt und läuft auf einem Windows (2012) Server. Das besondere an dieser Lösung ist, dass sie die hochauflösenden Bilder bereits serverseitig verkleinert und nur die verkleinerten Vorschaubilder an den Browser liefert. Das reduziert bei tausenden von Alarmbildern den Traffic enorm. Durch Mausklick auf die Vorschau erhält man das originale, hochauflösende Bild.

So sieht das dann aus:

Hier der Sourcecode.

default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DirectoryListing.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Directory Listing</title>
    <link href="./style.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label ID="LabelDropDownList" runat="server" Text="Datumsfilter: "></asp:Label>
        <asp:DropDownList ID="DropDownListDate" onselectedindexchanged="DropDownListDate_SelectedIndexChanged" runat="server"></asp:DropDownList>
        <asp:GridView ID="GridViewPreview" onpageindexchanging="GridViewPreview_PageIndexChanging" runat="server"></asp:GridView>
    </div>
    </form>
</body>
</html>

default.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Collections;
using System.Data;

namespace DirectoryListing
{

    public partial class Default : System.Web.UI.Page
    {

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                DataTable dt = GetFilesAndFolders();
                Session["DATATABLE"] = dt;
                Session["DATAVIEW"] = GetDataView(dt, new DateTime(1, 1, 1), 0);
            }
        }

        public DataTable GetFilesAndFolders()
        {
            DirectoryInfo dirInfo = new DirectoryInfo(DirectoryListingLib.getImagePath());

            FileInfo[] fileInfo = dirInfo.GetFiles("*.jpg", SearchOption.TopDirectoryOnly);

            DataTable dt = new DataTable();
            DataRow dr;

            DataColumn dc = new DataColumn("ID", typeof(System.Int64));
            dc.AutoIncrement = true;
            dt.Columns.Add(dc);

            dt.Columns.Add("Create_Date", typeof(DateTime));
            dt.Columns.Add("Create_DateTime", typeof(DateTime));
            for (int i = 0; i < DirectoryListingLib.NUM_COLUMNS; i++)
            {
                dt.Columns.Add("Preview_Pic" + i.ToString(), typeof(String));
            }
            dt.DefaultView.Sort = "Create_DateTime DESC";

            int colCounter = -1;
            dr = dt.NewRow();
            foreach (FileInfo file in fileInfo)
            {
                if (colCounter++ >= DirectoryListingLib.NUM_COLUMNS - 1)
                {
                    dt.Rows.Add(dr);
                    colCounter = 0;
                    dr = dt.NewRow();
                }

                dr["Create_Date"] = file.CreationTime.ToString("dd.MM.yyyy");                   // Bind to DropDownBox
                dr["Create_DateTime"] = file.CreationTime.ToString("dd.MM.yyyy HH:mm:ss");      // For sorting only
                dr["Preview_Pic" + colCounter.ToString()] = "<a href=\"" + DirectoryListingLib.getImageVPath() + "/" + file.Name +
                    "\"><img src=\"./getPreviewImage.aspx?img=" + Server.UrlEncode(file.Name) + "\"></a><br>" + file.CreationTime.ToString("dd.MM.yyyy HH:mm:ss");
            }

            dt.Rows.Add(dr);

            return dt;
        }

        public DataView GetDataView(DataTable dt, DateTime selectedDateTime, int selectedIndexDropDownListDate)
        {

            EnumerableRowCollection<DataRow> query = from rows in dt.AsEnumerable()
                                                     where rows.Field<DateTime>("Create_DateTime").Date == selectedDateTime.Date || selectedDateTime.Date.Year < 1970
                                                     orderby rows.Field<DateTime>("Create_DateTime") descending
                                                     select rows;

            DataView dv = query.AsDataView();

            if (dv.Count > 0)
            {
                this.GridViewPreview.Columns.Clear();
                foreach (DataColumn col in dt.Columns)
                {
                    //Declare the bound field and allocate memory for the bound field.
                    BoundField bfield = new BoundField();

                    bfield.DataField = col.ColumnName;
                    bfield.HeaderText = col.ColumnName;
                    bfield.HtmlEncode = false;
                    this.GridViewPreview.Columns.Add(bfield);
                }

                this.GridViewPreview.BackColor = System.Drawing.Color.LightGray;
                this.GridViewPreview.AutoGenerateColumns = false;                           // Avoid additional columns
                this.GridViewPreview.AllowPaging = true;
                this.GridViewPreview.AllowSorting = true;
                this.GridViewPreview.PageSize = 20;
                this.GridViewPreview.PageIndex = 0;
                this.GridViewPreview.PagerSettings.Mode = PagerButtons.NumericFirstLast;
                this.GridViewPreview.PagerSettings.Position = PagerPosition.TopAndBottom;
                this.GridViewPreview.Columns[0].Visible = false;                            // Hide ID 
                this.GridViewPreview.Columns[1].Visible = false;                            // Hide Date
                this.GridViewPreview.Columns[2].Visible = false;                            // Hide DateTime
                this.GridViewPreview.ShowHeader = false;
                this.GridViewPreview.CssClass = "DataTable";
                this.GridViewPreview.DataSource = dv;
                this.GridViewPreview.DataBind();

                this.DropDownListDate.DataSource = dt.DefaultView.ToTable(true, "Create_Date"); 
                this.DropDownListDate.DataTextField = "Create_Date";
                this.DropDownListDate.DataTextFormatString = "{0:dd.MM.yyyy}";
                this.DropDownListDate.DataValueField = "Create_Date";
                this.DropDownListDate.AutoPostBack = true;
                this.DropDownListDate.DataBind();
                this.DropDownListDate.Items.Insert(0, new ListItem("Alle", "01.01.0001"));
                this.DropDownListDate.SelectedIndex = selectedIndexDropDownListDate;

            }

            return dv;
        }

        public void GridViewPreview_PageIndexChanging(Object sender, GridViewPageEventArgs e)
        {
            this.GridViewPreview.DataSource = (DataView)Session["DATAVIEW"];
            this.GridViewPreview.PageIndex = e.NewPageIndex;
            this.GridViewPreview.DataBind();
        }

        public void DropDownListDate_SelectedIndexChanged(Object sender, EventArgs e)
        {
            string stringDateTime = this.DropDownListDate.SelectedItem.ToString();
            DateTime tempDateTime;

            DateTime.TryParse(stringDateTime, out tempDateTime);
            GetDataView((DataTable)Session["DATATABLE"], tempDateTime, this.DropDownListDate.SelectedIndex);
        }
        
    }

    public static class DirectoryListingLib
    {
        public static int NUM_COLUMNS = 4;                  // Number of preview columns

        public static bool IsProductiveEnvironment()        // Check environment (test or productive)
        {
            string hostName = HttpContext.Current.Request.ServerVariables["SERVER_NAME"].ToLower();
            return !hostName.StartsWith("localhost");
        }

        public static string getImagePath()                 // Absolute path of pictures
        {
            return HttpContext.Current.Server.MapPath(DirectoryListingLib.getImageVPath());
        }

        public static string getImageVPath()                // Virtual path of pictures
        {
            return (DirectoryListingLib.IsProductiveEnvironment() ? @"/webcam/img" : @"/images");
        }

    }

}

getPreviewImage.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="getPreviewImage.aspx.cs" Inherits="DirectoryListing.getPreviewImage" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
    </div>
    </form>
</body>
</html>

getPreviewImage.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
// using System.Web.UI.WebControls;
using System.Security;
using System.Net;
using System.Text;
using System.IO;
using System.Drawing;

namespace DirectoryListing
{
    public partial class getPreviewImage : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            String imgName = Request.QueryString["img"].Trim();

            Response.Clear();

            if (imgName.Length > 0)
            {
                String fileFullPath = DirectoryListingLib.getImagePath() + "/" + imgName;

                if (File.Exists(fileFullPath))
                {
                    Image imgObj = Bitmap.FromFile(fileFullPath);

                    imgObj = (Image)(new Bitmap(imgObj, new Size(128, 72)));

                    byte[] byteArray = imageToByteArray(imgObj);

                    // Image out to http....
                    Response.AddHeader("Content-Disposition", "attachment; filename=camimage.jpg");
                    Response.AddHeader("Content-Length", byteArray.Length.ToString());
                    Response.ContentType = "image/jpeg";
                    Response.BinaryWrite(byteArray);
                }
            }
            Response.End();

        }

        // Convert image to byte array
        private byte[] imageToByteArray(System.Drawing.Image image)
        {
            MemoryStream ms = new MemoryStream();
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
            return ms.ToArray();
        }

    }
}

style.css

body {
    font-family: Arial, Helvetica, sans-serif;
    font-size: 12px;
    font-weight: normal;
}

.TableScroll
        {
            max-height: 275px;
            overflow: auto;
            border:1px solid #ccc;
           
        }
        .DataTable
        {
            border-collapse: collapse;
        }
            .DataTable tr th
            {
                background-color: #3c454f;
                color: #ffffff;
                padding: 5px 5px 5px 5px;
                border: 1px solid #cccccc;
                font-family: Arial, Helvetica, sans-serif;
                font-size: 12px;
                font-weight: normal;
                text-transform:capitalize;
            }
            .DataTable tr:nth-child(2n+2)
            {
                background-color: #f3f4f5;
            }

            .DataTable tr:nth-child(2n+1) td
            {
                background-color: #d6dadf;
                color: #454545;
            }
            .DataTable tr td
            {
                padding: 5px 10px 5px 10px;
                color: #454545;
                font-family: Arial, Helvetica, sans-serif;
                font-size: 11px;
                border: 1px solid #cccccc;
                vertical-align: middle;
                text-align:center;
            }
                .DataTable tr td:first-child
                {
                    text-align: center;
                }

Als Erweiterung wäre z.B. eine AJAX-Funktion denkbar, die die großen Bilder als modales Fenster hochpoppen lässt.

Source als sln für Visual Studio.

Anbei die aktuelle Source als sln-Filestruktur für Visual Studio 2012. Damit kann man das Programm direkt als Projekt in Visual Studio laden.
Ich habe noch einige kleinere Bugs korrigiert (u.a. Session-Timeout-Problem des Datatables und Aktualisierung des Gridviews bei Datumsauswahl).

cp

Update

Und wieder gibt es eine neue Version. Jetzt habe ich noch die jquery- und eine Ajax-Bibliothek integriert, so das die Bilder flüssig in die Seite eingezoomt werden und man nicht mehr mit dem Vor- und Zurück-Button des Browsers hantieren muss. Zudem wurde der Viewer hinsichtlich der Verwendung bei Mobile Devices (iPad, iPhone usw.) verbessert, d.h. die Großansicht der Alarmbilder wird nun ggf. auf die Größe des Bildschirms skaliert. Ein- und Ausblendung reagieren auch auf Touch-Eingaben. Ein zusätzlicher Butten erlaubt einen Seiten-Refresh.

So sieht das dann aus:

Die Sourcen befinden sich im Anhang (Projektverzeichnis für Visual Studio 2012).

cp