ASP.NET FileAccessWeb Sample, část 3: Download souborů

Jan Holan       19. 7. 2012       ASP.NET/IIS, HTTP/HTML       6992 zobrazení

V této předposlední části naší ukázkovou aplikaci z minula v podstatě dokončíme implementací stránky Default.aspx, která bude zobrazovat seznam souborů. A dále do aplikace vložíme handler na stahování souborů.

Třída FileItem

Nyní pro změnu začneme C# kódem. Připravíme si třídu FileItem, která bude reprezentovat prvek jednoho zobrazovaného souboru. (Celou třídu opět najdete součástí zdrojových souborů příkladu.)

public class FileItem
{
    #region member varible and default property initialization
    public string Name { get; set; }
    public string Size { get; set; }
    public string Description { get; set; }
    public DateTime DateModified { get; set; }
    public string IconUrl { get; set; }
    #endregion

    #region constructors and destructors
    public FileItem(System.IO.FileInfo fileInfo)
    {
        this.Name = fileInfo.Name;
        this.Size = GetFileSize(fileInfo.Length);
        this.Description = GetFileDescription(fileInfo.FullName);
        this.IconUrl = GetIconUrl(fileInfo.Extension);
        this.DateModified = fileInfo.LastWriteTime;
    }
    #endregion
}

Třída potřebné informace načte z FileInfo souboru. K tomu musíme ještě do této třídy doplnit pomocné metody GetFileSize, GetFileDescription a GetIconUrl. String pro zobrazení FileSize získáme takto z long velikosti souboru:

private static string GetFileSize(long sizeInBytes)
{
    float size = sizeInBytes;
    int rad = 0;
    while (size > 1024f && rad <= 4)
    {
        size /= 1024f;
        rad++;
    }

    string byteSize = null;
    switch (rad)
    {
        case 0:
            byteSize = "B";
            break;
        case 1:
            byteSize = "KB";
            break;
        case 2:
            byteSize = "MB";
            break;
        case 3:
            byteSize = "GB";
            break;
        case 4:
            byteSize = "TB";
            break;
    }

    return (Math.Round((double)size, 2).ToString(System.Globalization.CultureInfo.CurrentCulture) + " " + byteSize);
}

Pro získání popisu typu souboru v metodě GetFileDescription budeme muset šáhnout pro Win32 API funkci SHGetFileInfo.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct SHFILEINFO
{
    public IntPtr hIcon;
    public int iIcon;
    public uint dwAttributes;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string szDisplayName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
    public string szTypeName;
}

[Flags]
private enum SHFILEINFO_FLAGS
{
    Icon = 0x100,
    LargeIcon = 0,
    SmallIcon = 1,
    TypeName = 0x400,
    UseFileAttributes = 0x10
}

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SHGetFileInfo(string pszPath, FileAttributes dwFileAttributes, out SHFILEINFO psfi, uint cbfileInfo, SHFILEINFO_FLAGS uFlags);

private static void GetFileInfo(string file, out SHFILEINFO fi, SHFILEINFO_FLAGS flags, FileAttributes fileAttr)
{
    if (!SHGetFileInfo(file, fileAttr, out fi, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), flags))
    {
        int error = Marshal.GetLastWin32Error();
        throw new IOException(string.Format(System.Globalization.CultureInfo.InvariantCulture, "SHGetFileInfo failed for file {0}", new object[] { file }), new System.ComponentModel.Win32Exception(error));
    }
}

private static string GetFileDescription(string file)
{
    SHFILEINFO shfileinfo;
    SHFILEINFO_FLAGS flags = SHFILEINFO_FLAGS.TypeName | SHFILEINFO_FLAGS.UseFileAttributes;
    GetFileInfo(file, out shfileinfo, flags, ((FileAttributes)0));
    return shfileinfo.szTypeName;
}

Poslední metoda GetIconUrl bude vracet Url na png obrázky podle přípony souboru (např. /images/FileTypeIcons/.zip.png) a pokud takový soubor nebude existovat vrátí se /images/FileTypeIcons/Generic.png.

private static string GetIconUrl(string extension)
{
    const string cBaseUrl = "~/images/FileTypeIcons";

    string imagePath = HttpContext.Current.Server.MapPath(cBaseUrl);
    string imageFile = Path.Combine(imagePath, extension + ".png");
    var fi = new FileInfo(imageFile);

    if (fi.Exists)
    {
        return ResolveUrl(cBaseUrl + "/" + fi.Name);
    }

    return ResolveUrl(cBaseUrl + "/Generic.png");
}

private static string ResolveUrl(string relativeUrl)
{
    const string appRelativeCharacterString = "~/";

    string url = new System.Web.UI.Control().ResolveUrl(relativeUrl);
    if (url.StartsWith(appRelativeCharacterString))     //Pokud je aplikace v IIS jako site, funkce Control.ResolveUrl URL nezmění
    {
        url = url.Substring(appRelativeCharacterString.Length);
    }

    return url;
}

Stránka Default.aspx

V Init stránky nejprve vypneme Cache stránky, aby byl vždy načten aktuální seznam souborů. Dále doplníme kód Page_Load, který bude mít za úkol připravit seznam souborů (typu List<FileItem>).

protected override void OnInit(EventArgs e)
{
    //Vypnutí cache stránky
    Page.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
}

protected void Page_Load(object sender, EventArgs e)
{
    var files = new List<FileItem>();

    if (Page.User.Identity.IsAuthenticated)
    {
        //Get content of App_Data folder to all users,
        //but you can use Page.User.Identity.Name to get different directory by logged user.
        string directory = this.MapPath("~/App_Data");

        if (System.IO.Directory.Exists(directory))
        {
            foreach (string fileName in System.IO.Directory.EnumerateFiles(directory))
            {
                var fi = new System.IO.FileInfo(fileName);

                files.Add(new FileItem(fi));
            }
        }
    }

    this.FileList.DataSource = files;
    this.FileList.DataBind();
}

Soubory, které budeme uživateli zobrazovat budou umístěny ve složce App_Data webu. Nyní jednoduše zobrazíme obsah všem uživatelům stejný, ale tato logika by se dala jednoduše rozšířit a podle přihlášeného uživatele vracet obsah jiný, nebo jinou složku.

Na konci kódu nastavíme vytvořený seznamem jako zdroj kontrolu ListView nazvaný FileList. Jeho definice bude tvořit obsah Default.aspx stránky.

<%@ Page Title="File Access Web" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="FileAccessWeb.Default" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
    <title>File Access Web</title>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <asp:ListView ID="FileList" runat="server" ViewStateMode="Disabled">
        <EmptyDataTemplate>
            <div style="padding: 1em;">
                Složka neobsahuje žádné soubory.
            </div>
        </EmptyDataTemplate>
        <LayoutTemplate>
            <table cellpadding="0" cellspacing="0" class="FileListTable">
                <tr>
                    <td valign="top">
                        <table id="headerBar" border="0" cellspacing="0" cellpadding="0" class="FileListHeaderRow">
                            <tr>
                                <td class="FileListNameColumn">
                                    <div class="ColumnPanel FileListNameColumn">N&#225;zev</div>
                                </td>
                                <td class="FileListSizeColumn BorderLeft">
                                    <div class="ColumnPanel FileListSizeColumn">Velikost</div>
                                </td>
                                <td class="FileListTypeColumn BorderLeft">
                                    <div class="ColumnPanel FileListTypeColumn">Typ</div>
                                </td>
                                <td class="FileListDateColumn BorderLeft">
                                    <div class="ColumnPanel FileListDateColumn">Datum změny</div>
                                </td>
                            </tr>
                        </table>

                        <div id="FileListBodyTable" class="FileListBody" style="width: 100%; overflow: auto; cursor: default">
                            <table id="listRows" border="0" cellspacing="0" cellpadding="0">
                                <asp:PlaceHolder ID="ItemPlaceHolder" runat="server" />
                            </table>
                        </di>
                    </td>
                </tr>
            </table>
        </LayoutTemplate>
        <ItemTemplate>
            <tr class="FileListRow">
                <td class="FileListNameColumn">
                    <div class="ColumnPanel FileListNameColumn">
                        <img class="fileIcon" alt="" src='<%# Eval("IconUrl") %>' />
                        <a href='<%# this.GetRouteUrl("Download", new { name = Eval("Name") }) %>'>
                            <asp:label ID="lName" runat="server" Text='<%# Eval("Name") %>' ToolTip='<%# GetStringFieldToolTip(Eval("Name"), 35) %>'/>
                        </a>
                    </div>
                </td>
                <td class="FileListSizeColumn">
                    <div class="ColumnSizePanel FileListSizeColumn">
                        <asp:Literal ID="lSize" runat="server" Text='<%# Eval("Size") %>'/>
                    </div>
                </td>
                <td class="FileListTypeColumn">
                    <div class="ColumnPanel FileListTypeColumn">
                        <asp:Literal ID="lType" runat="server" Text='<%# Eval("Description") %>'/>
                    </div>
                </td>
                <td class="FileListDateColumn">
                    <div class="ColumnPanel FileListDateColumn">
                        <asp:Literal ID="lModifiedDate" runat="server" Text='<%# Eval("DateModified") %>'/>
                    </div>
                </td>
            </tr>
        </ItemTemplate>
    </asp:ListView>
</asp:Content>

ListView kontrol se skládá ze dvou částí LayoutTemplate a ItemTemplate. LayoutTemplate určuje HTML kód, který se na stránku vyrendruje, pokud list obsahuje alespoň jeden prvek. Místo vlastních položek je v template pouze pomoci kontrolu PlaceHolder určeno místo, kam budou opakovaně generovány. HTML obsah položek je definován v ItemTemplate. V ní je možné využívat binding funkce (jako Eval) pro přístup k vlastnostem prvku listu.

Všimněte si následujícího volání v odkazu prvního sloupce řádku:
this.GetRouteUrl("Download", new { name = Eval("Name") })

Zde se získává hodnota Routy Download, kterou budeme používat pro odkaz na stažení souboru. Její správnou definici doplníme později až budeme přidávat handler pro stažení souborů. Ale aby nám stránka zatím fungovala doplňte do deklarace routes v Global.asax tento provizorní řádek:

RouteTable.Routes.MapPageRoute("Download", "{name}", "~/Default.aspx");

Ještě do Code behind stránky doplníme pomocnou funkci GetStringFieldToolTip pro vrácení textu tooltipu, pokud se má zobrazit (funkci voláme v ItemTemplate).

public string GetStringFieldToolTip(object value, int maxLength)
{
    string str = value as string;

    if (!string.IsNullOrEmpty(str) && str.Length > maxLength)
    {
        return str;
    }

    return null;
}

CSS styly

Zbývá do souboru Css/StyleSheet.css doplnit nové styly použité ve stránce.

/*File list*/
.FileListTable {
    border: 1px solid rgb(204, 204, 204);
    width: 100%;
    font-size: 0.84em;    
}

.FileListHeaderRow {
    height: 21px;
}

.FileListRow {
    height: 21px;
    cursor: pointer;
}

tr.FileListRow:hover {
    background-color: rgb(243, 247, 253);
}

.FileListNameColumn {
    width: 240px;
}
.FileListSizeColumn {
    width: 75px;
}
.FileListTypeColumn {
    width: 170px;
}
.FileListDateColumn {
    width: 175px;
}

.BorderLeft {
    border-left-color: rgb(239, 235, 239);
    border-left-width: 1px;
    border-left-style: solid;
}

.ColumnPanel {
    padding-left: 7px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.ColumnSizePanel {
    text-align: right;
    padding-right: 12px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.FileListBody {
    border-top-color: rgb(239, 235, 239);
    border-top-width: 1px;
    border-top-style: solid;
}

.fileIcon {
    border-width: 0px;
    width: 16px;
    height: 16px;
    margin-right: 4px;
    margin-left: 4px;
    vertical-align: middle;
}

Zde bych upozornil na dva styly:

ColumnPanel a ColumnSizePanel – má nastaveno text-overflow: ellipsis, to způsobí, že pokud bude text sloupce větší než sloupec, vypíše se pouze část, která se do sloupce vejde následována třemi tečkami.

tr.FileListRow:hover - mění background barvu při najedí na řádek myší.

Do složky Images nakopírujeme obrázky typů souborů FileTypeIcons (najdete je v archivu celého příkladu).

Javascript

Do stránky Default.aspx na konec Content kontrolu MainContent vložíme kousek Javascript kódu, který bude měnit výšku seznamu souborů (element FileListBodyTable) podle velikosti okna prohlížeče. Díky nastavení overflow: auto u elementu FileListBodyTable bude pak v případě, že je ve složce více souborů než se vejde na stránku, zobrazen u seznamu posuvník.

FileAccessWeb_Scroll

Javascript vypadá takto:

<script type="text/javascript">
    var reenter = false;
    function setClientHeight() {
        if (reenter) return;
        reenter = true;
        var table = document.getElementById('FileListBodyTable');
        if (table == null)
            return;

        var main = document.getElementById('main');

        var height = table.clientHeight;
        height -= main.offsetHeight - window.innerHeight + 2;

        if (height < 160)
            height = 160;

        table.style['height'] = height + 'px';
        reenter = false;
    }

    window.onresize = function (e) { setClientHeight(); }
    setClientHeight();
</script>

Download handler

Zbývá doplnit funkčnost stahování souborů.

Začneme přidáním vlastní třídy handleru DownloadHandler do projektu. Třídu si můžete prohlédnou zde.
Třída kromě odeslání obsahu souboru také podle jeho typu nastavuje příslušnou hlavičku ContentType a hlavně content-disposition, kde se parametrem attachment určuje, zda se má soubor nabídnou ke stažení, nebo zobrazit rovnou v prohlížeči (což je výhodné na soubory obrázků a textové soubory).

Pozn.: V kódu třídy DownloadHandler si ještě všimněte následujícího řádku:
var routeData = context.Request.RequestContext.RouteData;
Takto se získává objekt RouteData v code behind, kde máme k dispozici pouze HttpContext (viz zde).

URL, kterou stránka volá pro stažení souboru, jsme zvolili takto:
/FileAccessWeb/<soubor>

Handler nyní na tuto URL zaregistrujeme, provedeme to kódem v definici Routes v souboru Global.asax. K tomu budeme potřebovat metodu MapHttpHandler implementovanou v pomocné třídě RoutingExtensions(kód třídy původně pochází z blogu Phil Haacka zde). Třídu přidejte do projektu a v souboru Global.asax přepište definici routy “Download” na:

RouteTable.Routes.MapHttpHandler<DownloadHandler>("Download", "{name}");

Aby se nám tento handler správně volal na soubory libovolného typu, tak ještě musíme ve Web.config souboru u modules elementu nastavit:

<modules runAllManagedModulesForAllRequests="true">

Více se o tom můžete dočíst v tomto článku.

Tím máme aplikaci funkční, příště ještě doplníme ošetření chyb a statusu 404 – stránka nenalezena.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback