Egy valamirevaló, közösségi funkciókkal rendelkező webhelyen a felhasználók jogos kívánsága, hogy úgy tölthessenek fel fájlokat (főleg fényképeket), hogy ne kelljen egyesével kitallózgatni őket, és az sem árt, ha látnak valamilyen visszajelzést a feltöltés alatt, hogy még mennyi ideig kell türelmesen várniuk. Erre a VS beépített FileUpload vezérlő nem igazán alkalmas.
Kicsit körbenézve a lehetőségek után, két olyan free kontrollal találkoztam, ami ígéretesnek tűnt erre a célra.
Az egyik a SWFUploader, a másik a NeatUpload. Mindkét vezérlő nagyon jól dokumentált, mégis a működéshez vezető út rögös...
Ebben a cikkben a SWFUploader vezérlőről és VB.net integrálásáról lesz szó.
A nehézséget a dologban leginkább az jelenti, hogy az SWFUpload ugyan szépen dokumentált (még VB ügyben is némi kereséssel található segítség), de elsősorban arra van kitalálva, hogy fogunk egy mappát, és abba beletesszük a feltöltéseket. Főként persze képeket, aztán eltároljuk a mappa helyét, és odatesszük a felhasználónak a mappát, tessék, lehet nézegetni.. Azt helyből nem támogatja, hogy fájlonként eltároljunk különböző dolgokat adatbázisban. (pl. egyesével elérési út, képaláírás, ki töltötte fel, stb.) Egy strukturáltabb adatbázisban a kép (vagy egyéb más fájlok) mellé még szeretnénk ezt-azt eltárolni, meg kapcsolatokat rendelni. Sőt, az is lehet, hogy más-más típusú fájlt máshová szeretnénk tenni. Ehhez pedig szükség van arra, hogy a fájl feltöltésével azonos oldalon beviteli mezőkön beírathassuk ezeket, és a felhasználó lehetőleg egy kattintással menthesse el, a fájllal együtt.
Az csak hab a tortán, ha az egészet egy Masterpage alatti Content page-re szeretnénk tenni.
Maga a vezérlő nagyon elegáns, szép kivitelű, és elég jól testreszabható. A beállítási lehetőségekről később.
Az első teendő, hogy letöltsük a legfrissebb verziót innen: swfupload.org
VB.net alkalmazásunknak a következő fájlokra lesz szüksége:
swfupload.js
swfupload.swf
swfuploadbutton.swf
Ezt a 3 fájlt érdemes egy SWFUpload mappába tenni a gyökérbe, hogy ne kelljen bajlódni az elérési utakkal.
Az Images mappa tartalmazza azokat a képeket, amik a megjelenítésben kellenek (hiba, túl nagy fájl, stb.). A képeket ki is cserélhetjük ha akarjuk. Az Images mappát is célszerű a gyökérbe tenni ha itt sem akarunk bajlódni az elérési utakkal. Ha áthelyezzük, a megfelelő elérési utakat ne felejtsük el módosítani!
handlers.js
A példaalkalmazásban ezt a fájlt egy js nevű mappába tettem, de máshová is lehet tenni. Ebben a fájlban lehet testreszabni a vezérlőt.
uploader.css
A példaalkalmazásban a fájlt egy css nevű mappába tettem, persze máshová is lehet. Itt lehet megadni a vezérlőelemek stílusát. Tapasztalataim szerint nem nagyon érdemes belepiszkálni, mert esetleg nem várt eredményeket is kaphatunk. Az eredetit elmentve azért érdemes kísérletezni vele.
Thumbnail.vb
Ez egy osztály, ami majd a thumbnaileket fogja gyűjteni.
Szükség lesz még egy olyan handlerre, ami majd kiteszi nekünk a thumbnailokat a feltöltés alatt, illetve egy feltöltőoldalra.
A feltöltőoldal egy frame-ben fog beépülni az oldalba, amire a vezérlőt tesszük, ezért az upload.aspx csak vb kódot fog tartalmazni, kinézetre az oldal üres.
A thumbnailek készítése is egy másik oldalon történik. Erre a vezérlővel letölthető példaprojectben egy Thumbnail.aspx fájlt kapunk, de én készítettem helyette egy Gyorsthumb.ashx fájlt, ami majd kiteszi a kis képeket a megfelelő helyre.
Hozzunk létre egy oldalt, ahol a fájlokat fel szeretnénk tölteni.
Itt egy kis kitérőt teszek arra az esetre, ha masterpage/content típusú oldalakkal van dolgunk.
Mivel masterpage-ünk van, ezért az oldalon nincs head elem. Mivel css-hivatkozást csak head-ba tehetünk, ezért e két dolog valamelyikét kell tennünk:
1. Létrehozunk a masterpage-n egy head contentplaceholdert a head részben, majd ennek megfelelően beletesszük a feltöltőoldalunkba a head contentet. (ezt kipróbáltam, működik)
2. A css fájlt beletesszük a Theme alkönyvtárunkba, abba a témába, ami ezen a feltöltőoldalon aktív (ezt nem próbáltam).
A feltöltőoldal fejrészében létrehozzuk a megfelelő hivatkozásokat, amik content esetén így néznek ki:
<asp:Content ID="Content3" ContentPlaceHolderID="head" runat="Server">
<link href="css/uploader.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="swfupload/swfupload.js"></script>
<script type="text/javascript" src="js/handlers.js"></script>
</asp:Content>
(Ha nem masterpage/content típusú az oldal, akkor pedig a head-ban vannak a hivatkozások.)
Ezután beállítjuk a vezérlő testreszabható értékeit:
<script type="text/javascript">
var swfu;
window.onload = function() {
swfu = new SWFUpload({
upload_url: "upload.aspx?cel=fajl",
post_params: {
"ASPSESSID": "<%=Session.SessionID %>"
},
file_size_limit: "20 MB",
file_types: "*.*",
file_types_description: "Fájlok",
file_upload_limit: "1",
file_queue_error_handler: fileQueueError,
file_dialog_complete_handler: fileDialogComplete,
upload_progress_handler: uploadProgress,
upload_error_handler: uploadError,
upload_success_handler: uploadSuccessNoimage,
upload_complete_handler: uploadComplete,
button_image_url: "images/XPButtonNoText_160x22.png",
button_placeholder_id: "spanButtonPlaceholder",
button_width: 160,
button_height: 22,
button_text: '<span class="button">Tallóz... <span class="buttonSmall">(20 MB Max)</span></span>',
button_text_style: '.button { font-family: Helvetica, Arial, sans-serif; font-size: 14pt; } .buttonSmall { font-size: 10pt; }',
button_text_top_padding: 1,
button_text_left_padding: 5,
flash_url: "swfupload/swfupload.swf",
custom_settings: {
upload_target: "divFileProgressContainer"
},
debug: false
});
};
</script>
Masterpage esetén fontos, hogy ezt a scriptet ne a headba tegyük, inkább abba a contentbe, ami aktív (csak úgy, az oldal elejére :)).
Nézzük sorban a beállításokat:
upload_url: ez lesz az "üres" oldal, ami majd bekerül egy frame-be, ezen lesz a tulajdonképpeni vezérlő. A többi paraméter ahhoz kell, hogy adatokat tudjunk cserélni a feltöltő kóddal. Az ASPSESSID arra lesz jó, hogy beazonosítja a sessiont.
file_size_limit: az a méret, amit maximum megengedünk (nem lehet több, mint a web.config-ban megadott, annál pláne nem lehet több, mint amit a webszerveren futó IIS megenged, különben egy barátságtalan "I/O error" üzenetet fogunk kapni!)
file_types: itt megadhatjuk, hogy milyen kiterjesztésű fájlokat szeretnénk megengedni. A *.* mindent jelent, de fel is sorolhatjuk a megengedett fájltípusokat: pl.: *.jpg; *.jpeg
file_types_description: Itt azt adhatjuk meg, hogy amikor a felhasználónak majd megjelenik a tallózó ablak, akkor mi legyen beleírva. Ez lehet "Képek", "Minden fájl", vagy bármi más. Lehetőleg azért legyen összhangban a megengedett kiterjesztésekkel. :)
file_upload_limit: Az egyszerre kiválasztható fájlok száma. Ha 0, akkor korlátlan.
file_queue_error_handler: fileQueueError,
file_dialog_complete_handler: fileDialogComplete,
upload_progress_handler: uploadProgress,
upload_error_handler: uploadError,
upload_success_handler: uploadSuccess,
upload_complete_handler: uploadComplete, - ezek a handlerek a különböző események által kiváltott eseménykezelők meghívása. Ha egyedileg szeretnénk kezelni az eseményeket, megtehetjük. Itt annyit módosítottam, hogy az UploadSuccess eseményt másik ágra küldöm ha képet töltöttek fel, és másikra ha fájlt (hogy fájl esetén ne akarjon thumbnailt rajzolni).
button_image_url: A gomb háttérképe. Ha másik hátteret szeretnénk megadni, itt megtehetjük.
button_placeholder_id: az a hivatkozás, ahol a gomb felirata lesz. Ezt is érdemes így hagyni.
button_width: 160,
button_height: 22, - a gomb méretei
button_text: a gomb szövege (komplett html formában)
button_text_style: a gomb stílusa (ahogy css-ben lenne)
button_text_top_padding: felső üres hely a gombon
button_text_left_padding: balra üres hely a gombon
flash_url: az swfupload.swf fájl elérési útja EHHEZ az oldalhoz képest
custom_settings: egyéni egyéb beállítások
upload_target: az a hely az oldalon, ahol majd a feltöltés alatt a kijelző lesz, ezt is érdemes úgy hagyni ahogy van
debug: akarjuk-e látni a debug ablakot (ha azt választjuk hogy igen, akkor a feltöltés alatt kiírja a paraméterek értékeit az oldalra, nagyon hasznos, ha hibát keresünk)
Ezzel a testreszabás egyik felével megvagyunk. A másik fele a handlers.js fájlban lesz.
Ott a következő dolgokat érdemes megtenni:
1. A szövegeket lefordítani. (hibaüzenetek, visszajelzések, stb.)
2. A uploadSuccess(file, serverData) függvényben állítsuk be a thumbnail-kirajzoló handlert:
addImage("Ashx/Gyorsthumb.ashx?id=" + serverData);
sorral.
3. Az eredetihez képest hozzáadtam egy új eseménykezelőt uploadSuccessNoImage néven, ez csak abban különbözik a másiktól, hogy nem akar thumbnailt rajzolni. Ha nem képgaléria feltöltésére használjuk a vezérlőt, akkor a beállításokban erre az eseménykezelőre át lehet irányítani az upload_success_handler beállítását.
Ezzel a testreszabással megvagyunk már csak a kódokat kell elkészíteni.
A feltöltő oldalunkon szükség lesz egy helyre, ahová majd létrejön a vezérlő. Ez lehet pl. egy DIV elem.
<div id="swfu_container" style="margin: 0px 10px;">
<div>
<span id="spanButtonPlaceholder"></span>
</div>
<div id="divFileProgressContainer" style="height: 75px;">
</div>
<div id="thumbnails">
</div>
<br />
</div>
Ügyeljünk hogy az elnevezések pontosan azok legyenek, amit a scriptben megadtunk!
Ezután elkészítjük a feltöltőoldalt.
Ha nem csak képeket, hanem egyéb fájlokat is szeretnénk megengedni, akkor érdemes az oldalon elágaztatni a képek és az egyéb fájlok feltöltését, mert thumbnailt nem kell készítenünk más fájlokhoz, ráadásul máshova is akarjuk elmenteni őket.
A kód logikája:
Public Sub Page_Load(ByVal Sender As Object, ByVal E As EventArgs) Handles Me.Load
Select Case Request.QueryString("cel") 'elágazunk aszerint, hogy kép vagy más
Case "album"
Dim nevazon As String = Session.Contents("aktnev")
'a feltöltő oldalon hoztuk létre a név előtagját, ami alapján megtaláljuk majd a feltöltött fájlt
'elkészítjük a kép thumbnail méretű változatát, amit majd kiküldünk az oldalra, közben elmentjük az eredeti fájlt a szerverre a definiált néven.
[..]
Case "fajl"
Dim fajlnev As String = Session.Contents("aktnev")
'a fájl nevének létrehozási szabálya ugyanaz, de itt nincs szükség thumbnailekre, csak a feltöltött fájl mentésére
[..]
End Select
End Sub
Ezzel (kép esetében) elkészítettük a feltöltés közben kirajzolódó thumbnaileket, de még nem rajzoltuk ki a feltöltőoldalra.
Ezt fogja elvégezni a Gyorsthumb.ashx fájl.
<%@ WebHandler Language="VB" Class="Gyorsthumb" %>
Imports System
Imports System.Web
Public Class Gyorsthumb : Implements IHttpHandler, IRequiresSessionState
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim id As String = context.Request.QueryString("id")
Dim thumbnails As System.Collections.Generic.List(Of Thumbnail) = context.Session("file_info")
If id Is Nothing OrElse thumbnails Is Nothing Then
context.Response.StatusCode = 404
context.Response.Write("Not Found")
context.Response.End()
Return
End If
For Each thumb As Thumbnail In thumbnails
If thumb.ID = id Then
context.Response.ContentType = "image/jpeg"
context.Response.BinaryWrite(thumb.Data)
context.Response.End()
Return
End If
Next
context.Response.StatusCode = 404
context.Response.Write("Not Found")
context.Response.End()
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
Ezek után már nincs más dolgunk, mint a feltöltő oldalunk load eseményében gondoskodni a kezdőértékekről.
Session.Contents("aktnev") = Now.Ticks
Session.Contents("kiterjesztes") = ""
Session.Contents("file_info") = Nothing
Ezeken kívül még nem árt ellenőriznünk, hogy a web.configban biztosan engedélyeztünk-e akkora méretű fájlok feltöltését, mint itt az oldalon.
Ha nem, akkor az IIS alapértelmezetten csak 4 MB-ot engedélyez!
Ha többet szeretnénk megengedni ennél, írjuk be a web.config fájlba a következő sort a kívánt értékekkel kitöltve:
<httpRuntime executionTimeout="másodpercben az időtúllépés" maxRequestLength="maximális request méret (ebben nem csak a fájl, hanem maga az oldal is benne van!)" />
Ha a feltöltött fájlokat szeretnénk "elcsípni" hogy adatbázisban még ezt-azt eltároljunk róla, az értékeket megkaphatjuk session változókban. Ebben a példában pl.:
Session.Contents("aktnev"): a feltöltött fájl nevének kezdete
Session.Contents("kiterjesztes"): a feltöltött fájl kiterjesztése
Ne felejtsük el, hogy a fájl még a temp (vagy amit átmeneti tárolóként használtunk) mappában van. Ha a felhasználó nem döntött úgy, hogy "Mégsem", akkor a végleges helyére kell tennünk. Ez a szerver szempontjából már csak egy lokális művelet.
Közben még lekérdezzük a fájlról, amit tudni szeretnénk, átnevezzük, ha át akarjuk nevezni, stb.
Dim di = New IO.FileInfo(Server.MapPath(tempfajlpath))
fileSize = Format(di.Length / 1024, "F1")
filesizestr = CInt(fileSize).ToString & " kB"
Try
If IO.File.Exists(Server.MapPath(fajlpath)) = True Then
IO.File.Delete(Server.MapPath(fajlpath))
End If
Catch ex As Exception
End Try
IO.File.Copy(Me.MapPath(tempfajlpath), Server.MapPath(fajlpath))
Most már könnyedén be tudjuk írni az adatbázisba a fájl elérési útját, és minden egyebet amit tárolunk róla.
Mivel a fájlokat átmeneti tárolóba olvastuk be, felmerülhet a kérdés, hogy vajon honnan fogjuk tudni, hogy melyek a most beolvasott fájlok, és melyek azok, amik már ott voltak eddig is?
A trükk az, hogy a fájlokat nem az eredeti nevükön mentjük ebbe a tárolóba, hanem mi adunk nekik nevet:
Session.Contents("aktnev") = Now.Ticks
- ezt a nevet pedig sessionban átadjuk a feltöltőnek, ez alapján elnevezi és leteszi a fájlt az átmeneti tárolóba:
Request.Files("Filedata").SaveAs(Server.MapPath("~/kepek/" & _
Session.Contents("aktnev") & [még valamilyen egyedi karakterlánc] & kiterjesztes))
- ahonnan "kiválogathatjuk":
files = dir.GetFiles(Session.Contents("aktnev") & "_" & "*")
- és már meg is vannak a feltöltött fájlok.
A megoldás előnye és hátránya is egyszerre, hogy ott hagyjuk magunk után a temp mappában a fájlt. (Sajnos azonnal nem lehet törölni, mert a szerver még nem "engedi el" a fájlt néhány percig, a "fájl használatban van" hibát dobja.) Erre lehet írni egy karbantartó oldalt, amivel rendszeresen kitörölhetjük ezt a mappát.
Az viszont előny, hogy ha a felhasználó mégsem menti a feltöltést (meggondolja magát és egyszerűen bezárja a böngészőt), a fájl nem kerül be a végleges helyére, hanem pontosan tudni fogjuk hogy honnan törölhetjük egy karbantartás alkalmával.
Még egy különös dolgot vettem észre, nem tudom megmagyarázni, aki tudja miért lehet, ne fogja vissza magát.
Ha a web.config fájlban határozzuk meg az oldalak témáját pl. így:
<pages theme="zold">
akkor nem működik a thumbnail kirajzolási funkció. A feltöltés lefut, minden rendben van, de nem rajzol. :)
Ezért ha thumbnailt is szeretnénk, akkor:
<pages theme="">
A témát pedig így sajnos a lapok Page_PreInit eseményében kell beállítanunk egyesével. Úgy gondolom, ezt a kis kellemetlenséget mégis megéri ez a szép vezérlő.