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á.
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.