In een project waar ik aan werk hebben we de mogelijkheid om plaatjes te uploaden naar een server. Niet echt wereldschokkend, we hebben dit immers vaak genoeg gezien.
Het scenario is dit: een klant kan een verzoek doen tot een onderzoek, en om de onderzoeker te helpen kan hij foto’s van het te onderzoeken object meesturen. De flow is ongeveer dit:

In stap twee krijgt de gebruiker de mogelijkheid om een aantal foto’s te selecteren die met de aanvraag meegestuurd moeten worden. Pas na het bevestigen in stap 4 wordt de gehele aanvraag verstuurd naar de server.
Simpel.
In stap 2 moet ik de gebruiker dus een OpenFileDialog presenteren en de geselecteerde bestanden opslaan tot we de foto’s kunnen uploaden. In mijn ViewModel sla ik dus het pad naar de bestanden op, zodat ik die later kan versturen. Tenminste, dat was het plan. Zie onderstaande code snippet:
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog().GetValueOrDefault() == true)
{
string fullName = ofd.File.FullName;
// Store in the viewmodel
}
tja.. Niet dus. Als je dit runt krijg je een exception met de mooie tekst “File Operation not permitted. Access to path ‘’ is denied.” Je kunt wel de filename opvragen, maar niet het pad. En op dat moment maakte ik een domme fout. Ik dacht: “Ok, als ik dit niet kan doen, dan ga ik het bestand wel even kopieren naar de IsolatedStorage zodat ik ze later kan versturen.” Op zich niet verkeerd: de bestanden worden gekopieerd en pas verstuurd als de gebruiker dit bevestigd heeft. Daarna kan ik de bestanden verwijderen uit de IsolatedStorage en zoals ze zeggen “All is well…”. Voor diegene die niet weten wat de IsolatedStorage is: het is een container waar alleen jouw applicatie bij kan en die vertrouwd wordt door Silverlight. Dat is dus de plek waar je bestanden kunt wegschrijven. In principe heb je immers geen rechten om bestanden weg te schrijven in het filesystem: dit zou een enorm security probleem kunnen veroorzaken.
Nu is de ruimte die je hebt in de IsolatedStorage standaard niet al te groot. Default bij mij is dat slechts 1MB, niet genoeg om de foto’s in op te slaan. Gelukkig kan je de ruimte vergroten met een call naar IncreaseQuotaTo(long newQuotaSize); Deze geeft een boolean terug om aan te geven of het gelukt is of niet. Dus:
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog().GetValueOrDefault() == true)
{
var chosenFileSize = ofd.File.Length;
// Haal de locatie op voor de applicatie
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
if (isf.Quota < chosenFileSize)
if (isf.IncreaseQuotaTo(chosenFileSize))
{
// Kopieer het bestand
}
else
{
// Toon een error...
}
}
Maar… wat ik ook probeer, de IncreaseQuotaTo blijft false teruggeven. Ik krijg simpelweg niet de ruimte die ik heb, dus ik kan mijn bestanden niet kopieren. Tijd om te onderzoeken wat er gebeurd. Om het testen te versnellen haal ik de call naar de OpenFileDialog even weg, zodat mijn method er als volgt uit ziet:
// 16 MB moet voor nu genoeg zijn
var chosenFileSize = (16 * 1024 * 1024);
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
if (isf.Quota < chosenFileSize)
if (isf.IncreaseQuotaTo(chosenFileSize))
{
// Kopieer het bestand
}
else
{
// Toon een error...
}
En bij het runnen krijg ik nu de volgende dialog:

Ok. Dit werkt dus. Nou ja, ik heb het druk, ik zoek het later wel uit, dacht ik. Ik reserveer gewoon 16MB, ze mogen immers maar 4 foto’s maximaal uploaden dus dat moet genoeg zijn. Komt ‘ie:
// 16 MB moet voor nu genoeg zijn
var chosenFileSize = (16 * 1024 * 1024);
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
if (isf.Quota < chosenFileSize)
if (!(isf.IncreaseQuotaTo(chosenFileSize)))
{
// Toon fout en stop
return;
}
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog().GetValueOrDefault() == true)
{
// Kopieer het bestand...
}
Niet dus. Ik krijg nu een error bij de OpenFileDialog.ShowDialog: SecurityException: Dialogs must be user-initiated. WTF? Ik zit in de BtnClick event handler, hoezo “must be user-initiated?”.
Na enig nadenken wordt het me iets duidelijker. In mijn code heb ik 2 dialogs: eerst die van de IsolatedStorage, dan die van de OpenFileDialog. Vreemd genoeg werkt het ook niet als de quota al wel groot genoeg is en ik dus niet die dialog krijg.
Een zoektocht op het net bevestigde dit: bovenstaand scenario is niet mogelijk. Je moet het in twee stappen doen. Eigenlijk zou ik een button moeten maken die de storage vergroot (alleen weet ik niet met hoeveel want de foto’s zijn nog niet geselecteerd), en dan in een ander event de OpenFileDialog tonen. Maar waar in mijn flow moet ik dat doen? Het is onzin om de gebruiker te vragen om meer opslag ruimte als hij helemaal geen foto’s wil uploaden. Maar blijkbaar is dit de enige oplossing.
Microsoft, verzin hier iets op. Bovenstaand scenario is een vrij gangbaar iets: kies bestanden, dan reserveer je de ruimte.
Nu weet je het: dit gaat dus niet werken.
PS Ik heb het probleem wel opgelost: de OpenFileDialog.File is een FileInfo object, en die sla ik nu op in mijn viewmodel in plaats van het pad wat ik eerst van plan was. Daarmee kan ik later de foto’s gaan uploaden. Op die manier heb ik de hele IsolatedStorage niet meer nodig.
Waarom de FileInfo wel benaderd mag worden maar het pad niet, is mij een raadsel. Maar in ieder geval werkt mijn code nu volgens de flow die we bedacht hadden.