jQuery-File-Upload v ASP.NET

Jan Holan       28. 8. 2013       ASP.NET WebForms, ASP.NET/IIS, HTTP/HTML, JavaScript       8077 zobrazení

Do jedné ASP.NET aplikace jsem nedávno potřeboval implementovat File Upload. Od implementace jsem požadoval, aby využívala HTML5, podporovala výběr více souborů, drag & drop a přitom, aby fungoval i nějaký fallback pro starší prohlížeče.

Zavrhnul jsem AjaxFileUpload součástí ASP.NET AJAX Control Toolkit, který jsem do nové aplikace již dávat nechtěl, navíc v něm nefunguje drag & drop ani na IE verze 10 (funguje až na IE 11 preview). Podobně i jiné knihovny jako Ajax Uploader , Plupload a další také nepodporují drag & drop  na IE. Zvolil jsem nakonec knihovnu blueimp jQuery-File-Upload.

Knihovna podporuje více verzí (Basic Plus, Basic Plus UI, AngularJS), které zobrazují seznam souborů pro upload (možnost uploadu, cancel a delete na jednotlivých souborů). Tuto funkčnost jsem do své aplikace nepotřeboval, proto jsme implementoval pouze verzi Basic (seznam souborů jsem řešil v ASP.NET na serveru).

Nastavení web.config souboru

Aby bylo podporováno nahrání i větších souborů, musíme pro každý file uploadu (nejen s využitím této knihovny) provést následující nastavení pro IIS ve web.config souboru:

<system.web>
  <compilation debug="true" targetFramework="4.5" />
  <httpRuntime targetFramework="4.5" maxRequestLength="42949672" />

  ...
</system.web>
  
<system.webServer>
  ...

  <security> <requestFiltering> <requestLimits maxAllowedContentLength="4294967295" /> </requestFiltering> </security>
</system.webServer>

Přidání pluginu do ASP.NET projektu

Pro přidání File Upload pluginu do ASP.NET projektu jsem použil NuGet balíček JQuery File Upload Plugin (PM> Install-Package JQuery_File_Upload_Plugin) (v době psaní článku se jedná o verzi 8.3.2.1). Ten do projektu přidá klientskou část tj. potřebné javascripty a CSS styly a umístí je do struktury adresářů Contents a Scripts, kterou v dnešní době ASP.NET standardně používá (přes NuGet balíčky).
Edit: Novější verze NuGet balíčku obsahuje ještě složky App_Start, Controllers a Views pro MVC, ty pro uváděné řešení nejsou protřeba, navíc pokud upload přidáváte do Web Forms aplikace, tak tyto složky musíte smazat, any šel projekt zkompilovat.

Jak jsem již uvedl budu implementovat Basic variantu, pro tu je potřeba zavést následující CSS styly a JS scripty. Já jsem si na to vytvořil následující JS bundle:

//jQuery File Upload Plugin
bundles.Add(new ScriptBundle("~/bundles/fileupload/bootstrap/Basic/js").Include(
                "~/Scripts/FileUpload/jqueryui/jquery.ui.widget.js",    //The jQuery UI widget factory, can be omitted if jQuery UI is already included
                "~/Scripts/FileUpload/jquery.iframe-transport.js",      //The Iframe Transport is required for browsers without support for XHR file uploads
                "~/Scripts/FileUpload/jquery.fileupload.js"));          //The basic File Upload plugin

a CSS bundle (ten jsem umístil do Bundle.config):

<styleBundle path="~/Content/FileUpload/Basic/css">
  <!-- CSS to style the file input field as button and adjust the Bootstrap progress bars -->    
  <include path="~/Content/FileUpload/css/jquery.fileupload-ui.css" />
</styleBundle>

A bundly zavedeme do stránky například takto:
<webopt:BundleReference runat="server" Path="~/Content/FileUpload/Basic/css" />
a
<%: Scripts.Render("~/bundles/fileupload/bootstrap/Basic/js") %>

Nyní do stránky přidáme HTML kód pro tlačítko uploadu:

<asp:LinkButton ID="UploadRefreshButton" runat="server" ClientIDMode="Static" CausesValidation="false" style="display:none;" />
<div>
    <!-- The fileinput-button span is used to style the file input field as button -->
    <span class="btn btn-success fileinput-button">
        <i class="icon-plus icon-white"></i>
        <span>Přidat soubory...</span>
        <!-- The file input field used as target for the file upload widget -->
        <input id="fileupload" type="file" name="files[]" multiple />
    </span>
    <!-- The global progress bar -->
    <div id="progress" class="progress progress-success progress-striped">
        <div class="bar" style="width: 0%;"></div>
    </div>
    <!-- Initialize the jQuery File Upload Plugin -->
    <script type="text/javascript">
        initFileupload('<%: this.GetRouteUrl("Upload", null) %>',
            function () { document.getElementById('<%: this.UploadRefreshButton.ID %>').click(); });
    </script>
</div>

HTML kód kromě výše uvedených CSS stylů dále využívá i styly z twitter Bootstrap, který komponenta používá. Základ pro upload je tvořen prvkem input type="file", všimněte si ale HTML5 rozšíření multiple pro výběr více souborů. Podobně je zde možné ještě omezit typy vybíraných souborů přidáním atributu accept například takto accept='image/*'.

Tento input type="file" prvek zde ale není klasicky zobrazen, jak jste možná zvyklí z jiných webových stránek. Místo toho je pomoci stylů úplně skryt a místo něho je nastylován element span do podoby tlačítka. Vedle něj je dále element progressbaru, který je zobrazen pokud upload souborů právě probíhá.

image

V poslední části kódu je krátký Javascript, který volá funkci initFileupload pro inicializaci komponenty. Jako vstupní parametry dostává cílové URL na http handler, který upload provádí, a dále funkci, která se zavolá po provedení uploadu. Já zde URL handleru konkrétně získávám z Routy, kterou mám na to zavedenou, ale může tu být i jiné řešení. Jako funkci je použito volání skrytého tlačítka UploadRefreshButton, které tak způsobí například zavolání postbacku pro obnovení seznamu souborů (řešeno voláním click, aby postback fungoval i ve FF).

Poslední klientskou částí je implementace JS funkce initFileupload, kterou doporučuji umístit do samostatného souboru .js.

function initFileupload(url, doneFunction) {
    'use strict';

    $('#progress').hide();

    $('#fileupload').fileupload({
        url: url,
        dataType: 'json',
        singleFileUploads: false,
        always: function (e, data) {
            $('#progress').hide();
            $('#progress .bar').css('width', '0%');
            doneFunction();
        },
        progressall: function (e, data) {
            var progress = parseInt(data.loaded / data.total * 100, 10);
            $('#progress .bar').css('width', progress + '%');
            $('#progress').show();
        }
    });
}

Na serverové straně zbývá implementovat http handler, který zajistí zpracování poslaných souborů (jejich uložení). Základní kód handleru je následující:

public class UploadHandler : IHttpHandler
{
    #region action methods
    /// <summary>
    /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the System.Web.IHttpHandler interface.
    /// </summary>
    /// <param name="context">An System.Web.HttpContext object</param>
    public void ProcessRequest(HttpContext context)
    {
        if (context.Request.HttpMethod != "POST")
        {
            context.Response.StatusCode = 404;
            context.Response.StatusDescription = "Not Found";
            return;
        }

        if (context.Request.Files.Count != 0)
        {
            context.Response.ContentType = "text/plain";

            for (int i = 0; i < context.Request.Files.Count; i++)
            {
                var file = (HttpPostedFile)context.Request.Files[i];
                if (file.ContentLength == 0)
                {
                    continue;
                }

                string fileName;
                if (HttpContext.Current.Request.Browser.Browser.ToUpper() == "IE")
                {
                    string[] pathParts = file.FileName.Split(new char[] { '\\' });
                    fileName = pathParts[pathParts.Length - 1];
                }
                else
                {
                    fileName = file.FileName;
                }

                SaveFile(fileName, file.InputStream, file.ContentLength);
            }
        }
    }

    /// <summary>
    /// Gets a value indicating whether another request can use the System.Web.IHttpHandler instance.
    /// </summary>
    public bool IsReusable
    {
        get { return true; }
    }
    #endregion
}

Já jsem handler registroval pomoci Route například takto:
routes.MapHttpHandler<UploadHandler>("Upload", "upload-souboru");
Využívám zde pomocnou třídu s extension metodami RoutingExtensions, o které jsem již psal v článku ASP.NET FileAccessWeb Sample, část 3: Download souborů.

Při implementaci Basic varianty není potřeba z handleru nic vracet. Pro implementace rozšířených variant by bylo potřebné z handleru vrátit JSON odpověď s informacemi pro seznam souborů.


Pozn.: Speciálně pro ASP.NET MVC existuje ještě projekt Backload (a patřičný Nuget balíček, PM> Install-Package Backload). Jedná se o serverovou část obsahující controller a handler právě pro jQuery File Upload Plugin (a další). Tento balíček jsem ale nepoužil, jednak protože mi v aplikaci vystačila popsaná Basic varianta a jednak protože aplikace nebyla v MVC, ale ve WebForms.

 

hodnocení článku

0 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

RE: jQuery-File-Upload v ASP.NET

Tento článek (a jako vlastně většina článků na tomto blogu) opravdu není pro začátečníky, zaměření je pro vývojáře, který již s ASP.NET zkušenosti má. Také to není myšleno jako úplná kuchařka krok za krokem, pro vytvoření konkrétní sample aplikace, ale spíše je to obecnější popis. Příště se pokusím v článcích více popsat předpoklady pro čtenáře.

Nyní konkrétně, script s funkcí initFileupload může být umístěn kdekoliv, podmínkou je pouze to, aby byl pro stránku zaveden. Já to řeším tak, že takovéto funkce umisťuji do samostatných .js / .ts souborů, které mám ve složce Scripts/App. Celou tuto složku mám pomoci bundlu spojenou a minifikovanou do jednoho scriptu, který mám přímo pro celý web zaveden již na Site.Master (ukázaný to je v článku http://www.dotnetportal.cz/blogy/15/Null... JavaScripty a Bundling & minification).

V článku jsem neuvedl kód pro zavedení vytvořených bundle do stránky (doplněno). To může být důvod proč se Vám komponenta nenastylovala a nefungovala. Jinak by s tím problém být neměl, pokud chybu budete mít stále, zkuste jí konkrétně popsat, pak zkusím poradit.

HttpHandler je kapitola sama pro sebe, rozepisovat to v článku by vedlo na jeho velké prodloužení (a už takhle není krátký). Dříve se používaly soubory .ashx, pak se používali normální třídy odvozené z IHttpHandler (myslím tím v souboru .cs) a registrovali se ručně ve Web.config. S příchodem Routingu já ovšem preferuji řešení třídu handleru v souboru .cs registrovat kódem, používám na to pomocnou třídu RoutingExtensions, kterou najdete v článku http://www.dotnetportal.cz/blogy/15/Null.... Kód pro registraci Routy jsem doplnil do článku.

Chybu s tagem input jsem opravil (dokonce jsem to takto měl i v produkčním kódu, asi to zas tak nevadilo). Děkuji za připomínky článek jsem mírně upravil, aby celé řešení bylo kompletnější. Za jakoukoliv zpětnou vazbu jsem vždy rád pomůže to články zlepšovat, díky.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

RE: jQuery-File-Upload v ASP.NET

Zdravím. Nechci být šťoural, ale neodpustím si poznámku, že tento "návod" je šitý hodně horkou jehlou a pro začátečníka naprosto k ničemu, protože je neúplný. Pokud začátečník vytváří vše podle tohoto návodu, pak se z něj např. nedozví, kam umístit klientskou část skriptu initFileupload apod.

Taktéž, pokud začátečník vytvoří ve VS nový projekt pomocí průvodce, prostřednictvím NuGet stáhne potřebné balíčky, a doplní zde uvedené části kódu, upload nejen, že není nastylován tak, jak je v článku uvedeno, ale je mimo jiné i nefunkční (toto jsem osobně ověřoval)...

Není zde ani uvedeno, jak vytvořit httpHandler, a že pokud jej voláme prostřednictvím GetRouteUrl je nutné jej v global.asax namapovat. A co v případě, že chceme vytvořit UploadHandler.ashx?

Navíc, v html části skriptu není ukončen tag input...

Cením si práce a příspěvků všech autorů, ale pokud mají jejich články k něčemu být, pak ať jsou opravdu kompletními návody i pro začátečníky a autoři nepředpokládají, že článek píší pouze pro ostřílené programátory, kteří mají podobné věci v malíku a budou po nich ještě luštit a opravovat chyby v článcích...

Tímto se opravdu nechci nikoho dotknout, ale články tohoto ražení nemají smysl, pokud nejsou úplné a přesné...

JirkaM

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět
                       
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