×

First time here?

You are looking at the most recent posts. You may also want to check out older archives. Please leave a comment, ask a question and consider subscribing to the latest posts via RSS or email. Thank you for visiting!

Someone posed this question (“Can you use IsolatedStorage in Silverlight as a more reliable browser caching technique?”) to me and I answered with my usual optimistic “in theory, yes” answer.  Of course I had never tried it which is horrible to answer that to someone without trying it.  In working on creating some Silverlight business application samples, I figured I should probably look at this scenario to see if a) it would work and b) it makes sense.  I’ll at least try to answer “a” here.

The Setup

Let’s look at the setup.  The goal here is to have a lean startup application and only request additional components/controls/applications when needed, thus producing the best startup experience for the end user.  I decided to use an existing simple sample I already had which does dynamic XAP loading for an application.  Basically the application loads a simple interface of a search box and button.  Since the user hasn’t interacted with data yet, we didn’t want to add the payload of DataGrid into the initial application.  Once the button is clicked the application is requested, loaded into memory and displayed.  The relevant code from that sample is shown here:

   1: private void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:     panel1.Children.Clear();
   4:  
   5:     WebClient c = new WebClient();
   6:     c.OpenReadCompleted += new OpenReadCompletedEventHandler(c_OpenReadCompleted);
   7:     c.OpenReadAsync(new Uri("DynamicXapDataGrid_CS.xap", UriKind.Relative));
   8: }
   9:  
  10: void c_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
  11: {
  12:     string appManifest = new StreamReader(Application.GetResourceStream(new StreamResourceInfo(e.Result, null), new Uri("AppManifest.xaml", UriKind.Relative)).Stream).ReadToEnd();
  13:  
  14:     Deployment deploy = XamlReader.Load(appManifest) as Deployment;
  15:  
  16:     Assembly asm = null;
  17:  
  18:     foreach (AssemblyPart asmPart in deploy.Parts)
  19:     {
  20:         string source = asmPart.Source;
  21:         StreamResourceInfo streamInfo = Application.GetResourceStream(new StreamResourceInfo(e.Result, "application/binary"), new Uri(source, UriKind.Relative));
  22:  
  23:         if (source == "DynamicXapDataGrid_CS.dll")
  24:         {
  25:             asm = asmPart.Load(streamInfo.Stream);
  26:         }
  27:         else
  28:         {
  29:             asmPart.Load(streamInfo.Stream);
  30:         }
  31:     }
  32:  
  33:     panel1.DataContext = FakeData.GetCustomers(LastNameSearch.Text);
  34:  
  35:     UIElement myData = asm.CreateInstance("DynamicXapDataGrid_CS.Page") as UIElement;
  36:  
  37:     panel1.Children.Add(myData);
  38:     panel1.UpdateLayout();
  39:  
  40: }

You can see that in line 7 above we are using OpenRead to request our satellite XAP which contains our control/logic for our DataGrid, and then starting in line 18 we look for the relevant assembly to load and instantiate (line 35) and put into the visual tree (line 37).

Browser Caching The Request

If we would execute the app (clicking the button) several times, we’d expect that the first time the browser would request the satellite XAP and deliver it.  We would also generally expect the browser to cache that request for later.  So that if we click the button again, it would retrieve from cache and operate faster.

But what about corporate policies and other types of things that may prevent the browser from caching that request?  What if we want a different mechanism for caching our object requests and satellite assemblies?

An alternate method, use IsolatedStorage

So how can we refactor the above to use Silverlight’s IsolatedStorage area as a cache for our satellite assemblies?  let’s take a look.  First if you follow similar techniques as you might in ASP.NET for caching, you’d first check the cache before you load something.  If it isn’t there, you’d load it, add it to the cache and then use it for later as well.  Let’s do that here.

Instead of making the OpenRead request on the button click, we should first check to see if the satellite XAP has been loaded into IsoStore:

   1: private void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:     panel1.Children.Clear();
   4:  
   5:     using (var store = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
   6:     {
   7:         if (!store.FileExists("DynamicXapDataGrid_CS.xap"))
   8:         {
   9:             WebClient c = new WebClient();
  10:             c.OpenReadCompleted += new OpenReadCompletedEventHandler(c_OpenReadCompleted);
  11:             c.OpenReadAsync(new Uri("DynamicXapDataGrid_CS.xap", UriKind.Relative));
  12:         }
  13:         else
  14:         {
  15:             LoadData();
  16:         }
  17:     }
  18: }

Line 5 shows us initating the IsolatedStorage area.  (For more information on IsolatedStorage usage, see this video.)  We first look to see if the file exists in the store.  If it is not in there, then we use our OpenRead functionality to start the request.  If the file is there, then we call our refactored LoadData() function, which we’ll look at in a moment.  The completed event for our OpenReadAsync call looks like this:

   1: void c_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
   2: {
   3:     using (var store = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
   4:     {
   5:         System.IO.IsolatedStorage.IsolatedStorageFileStream fileStream;
   6:  
   7:         // create the file
   8:         fileStream = store.CreateFile("DynamicXapDataGrid_CS.xap");
   9:  
  10:         // write out the stream to isostore
  11:         WriteStream(e.Result, fileStream);
  12:  
  13:         fileStream.Close();
  14:     }
  15:     LoadData();
  16: }
  17:  
  18: private void WriteStream(Stream stream, System.IO.IsolatedStorage.IsolatedStorageFileStream fileStream)
  19: {
  20:     byte[] buffer = new byte[4096];
  21:     int bytesRead;
  22:  
  23:     while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
  24:     {
  25:         fileStream.Write(buffer, 0, bytesRead);
  26:     }
  27: }

What we see here is that in our completed event is where we actually create the file and put it in our IsoStore location using a little helper method to write the stream data out.  Now we have a satellite assembly in our IsolatedStorage for our application.

You can see that in the completed event we also call LoadData().  This is really the same core code we previously had that we refactored into a common method:

   1: private void LoadData()
   2: {
   3:     using (var store = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
   4:     {
   5:         IsolatedStorageFileStream fileStream = store.OpenFile("DynamicXapDataGrid_CS.xap", FileMode.Open, FileAccess.Read);
   6:  
   7:         #region Original Code
   8:         StreamResourceInfo sri = new StreamResourceInfo(fileStream, "application/binary");
   9:  
  10:         Stream manifestStream = Application.GetResourceStream(sri, new Uri("AppManifest.xaml", UriKind.Relative)).Stream;
  11:         string appManifest = new StreamReader(manifestStream).ReadToEnd();
  12:  
  13:         Deployment deploy = XamlReader.Load(appManifest) as Deployment;
  14:  
  15:         Assembly asm = null;
  16:         
  17:         foreach (AssemblyPart asmPart in deploy.Parts)
  18:         {
  19:             string source = asmPart.Source;
  20:  
  21:             // next line is a hack for Beta 2 and is not needed in Silverlight 2 release
  22:             fileStream = store.OpenFile("DynamicXapDataGrid_CS.xap", FileMode.Open, FileAccess.Read);
  23:  
  24:             StreamResourceInfo streamInfo = Application.GetResourceStream(new StreamResourceInfo(fileStream, "application/binary"), new Uri(source, UriKind.Relative));
  25:  
  26:             if (source == "DynamicXapDataGrid_CS.dll")
  27:             {
  28:                 asm = asmPart.Load(streamInfo.Stream);
  29:             }
  30:             else
  31:             {
  32:                 asmPart.Load(streamInfo.Stream);
  33:             }
  34:         }
  35:  
  36:         panel1.DataContext = FakeData.GetCustomers(LastNameSearch.Text);
  37:  
  38:         UIElement myData = asm.CreateInstance("DynamicXapDataGrid_CS.Page") as UIElement;
  39:  
  40:         panel1.Children.Add(myData);
  41:         panel1.UpdateLayout();
  42:         #endregion
  43:  
  44:     }
  45: }

You’ll note that we pull the file out of IsoStore and do our same loading from there.  Please note the comment for line 22.  This is a bug in Silverlight 2 Beta 2 where the Stream is closed too early and thus you’d get an exception on line 24 because the Stream was already closed in line 10.  The code above is a hack for sure (and inefficient).  We could have subclassed Stream and modified the Close functionality, but since I know it works in the release code I wasn’t going to bother you with some unnecessary huge workaround at this point, but line 22 can be removed at release time.

The Results

So putting it all together let’s look at how the requests look.  Let’s wipe out our IsolatedStorage so that nothing is in there:

Now when we click on our button in our application, we can see that since there is nothing in our IsoStore, the HTTP traffic goes through:

and we now have something in our IsoStore as evident by the preferences dialog:

or more specifically by spelunking to the actual location for IsolatedStorage, we can see our XAP located there:

Sweet.  Now future button clicks show no more HTTP traffic because it is pulling it out of IsolatedStorage.  If we clear our browser cache, it is still there.  If we delete our cookies, the XAP is still there.  Now when our app users return, they’ll have the satellite assemblies/XAPs we need for our apps already on their machine.

Caution and Thoughts

Okay, while this “works” there are definitely some cautionary decisions you’d have to make from an application architecture point of view.  Here are some thoughts…

    • What about versioning of your satellite assemblies and controls?  If you use the exact method here, any update would never be retrieved.  Some mechanism of adding data to the IsoStore information should be considered so you know your users have what your application expects.
    • What about the size of the XAP and the available storage space in IsoStore?  You should know the sizes of things you are going to place into IsoStore, and in the code above before placing anything in there, you should look at the available space and request a quota increase if needed.  Information on how this can be done is explained here.
    • What about new browser functionality like InPrivate and In Cognito windows from browsers?  Since both of these functions are in browsers that are beta, it is hard to say how their final implementations will effect the use of IsolatedStorage, but it is something to consider and test.

At any rate, an interesting pattern that you may want to consider for your application if it makes sense.  Is it more reliable than the browser cache?  I don’t think you can universally answer that because almost all client configurations for stuff like that seem to vary…but it is an alternative!  Here’s the code for the completed solution I modified.


This work is licensed under a Creative Commons Attribution By license.


9/24/2008 6:40 PM | # re: IsolatedStorage as a Silverlight object cache
Very interesting read and implementation. A couple other questions come to mind. Is Isolated Stored relative to the browser as a whole or by url or some other unique indicator? The reason I ask is what is there to prevent another company with a control named exactly the same. i.e. MyControl, and over writing your control. At this point one cant help but think about using a GUID as a unique identifier for 'my' control, testing for that specific GUID and updating as necessary. MD5 checksome comes to mind also as a possible solution.

Just some thoughts.
9/24/2008 8:21 PM | # re: IsolatedStorage as a Silverlight object cache
you mentioned "// next line is a hack for Beta 2 and is not needed in Silverlight 2 release"

it that because the Stream will be automatically disposed in
Application.GetResourceStream ?

I encountered that problem, all types of stream except the MS.Internal.InternalMemoryStream that returned by WebClient.

and there is no way to change that behaviour. If it is really a bug, I hope the final release of sl 2 will soon come out.
9/24/2008 9:17 PM | # re: IsolatedStorage as a Silverlight object cache
Matt: the IsolatedStorage is not shared across all Silverlight applications. There are two aspects of IsoStore with Silverlight: Applications and Site. Application storage is specific to the XAP URI so foo.com/site1/my.xap does not share the same isostore as foo.com/site2/myother.xap. However they both share the same Site settings in IsoStore because they are at the same domain. Make sense?
9/24/2008 9:19 PM | # re: IsolatedStorage as a Silverlight object cache
unruledboy: Yes the Application.GetManifestStream prevented the underlying stream from further reading in Beta 2. As I note above, this is fixed in release so that the hack indicated above would not be necessary.
9/24/2008 10:51 PM | # re: IsolatedStorage as a Silverlight object cache
Is there no "common component" cache in silverlight? If someone comes out with a library will everyone need to store their own copy of it somewhere?
9/24/2008 10:55 PM | # re: IsolatedStorage as a Silverlight object cache
Fowl: no, there is no 'global assembly cache' for Silverlight other than the core libraries in the runtime. This is because nothing is 'installed' on the end user machine, versus just being executed by the runtime in the browser sandbox. So yes, if a user visits 3 different apps that implement the same 3rd party control, they'll be getting that 3rd party control downloaded (by whatever means those 3 app developers chose) and it will not persist on the client machine.
9/29/2008 6:40 PM | # re: IsolatedStorage as a Silverlight object cache
Tim,

You mention above that you hack works in RTM, however this code does not appear to work in the RC0 build. I get an error to use Deployment.Current.

Thanks in advance,
Bart
9/29/2008 10:41 PM | # re: IsolatedStorage as a Silverlight object cache
I updated the code to work with RC0. I wrote a small article about it here...

www.silverlighthack.com/.../Silverlight-2-(RC0-RTM)-Dynamic-Assembly-Loading.aspx
10/1/2008 12:31 AM | # re: IsolatedStorage as a Silverlight object cache
Hi Tim,

Thank you for your enriching posts.

I have two questions:

1. You have mentioned "we’d expect that the first time the browser would request the satellite XAP and deliver it." - Is that correct? Is that the browser default behavior?

2. Every time we need the data greed we retrieve the whole *.xap file and load the assembly, I've tried to hack it up using "AppDomain.GetAssemblies" in order to verify whether the assembly has already been loaded before loading it again. However this method was removed from the silverlight CLR. Could you figure up another solution? (keeping track of them in a dictionary seems to me somehow "wrong").

Finally – I would like to say that as a currently flash developer, the use of dynamically loaded assemblies is very useful in terms of smooth user experience and especially where Multilanguage support for your application is needed.

Thanks,

Dudi
10/1/2008 9:39 AM | # re: IsolatedStorage as a Silverlight object cache
Dudi: 1) the browser wouldn't automatically request it -- sorry if I implied that. I meant that your app in the browser would make the initial request. 2) you can check in the Deployment.Current.Parts and if it exists, it has already been loaded (good suggestion I should change to this code anyway).
10/1/2008 2:08 PM | # re: IsolatedStorage as a Silverlight object cache
Hi Tim,

Regard your solution for checking the Deployment.Current.Parts.
This solution is good only if this assembly is part of your initial XAP file and as such gives no solution in the case of dynamically loaded assemblies.
In that case we will keep loading the assembly (either from remote or from the isolated storage), instead of just using already loaded assembly.

Have I missed something?

Dudi
10/1/2008 2:27 PM | # re: IsolatedStorage as a Silverlight object cache
Dudi: Assemblies loaded via the manifest or via AssemblyPart.Load are in that collection.
10/2/2008 12:32 PM | # re: IsolatedStorage as a Silverlight object cache
Hi Tim - I really appreciate your efforts.
However, I feel I do missing something as this solution fails to work for me (might be cause I'm using the beta?)

Anyway - I will probably have to wait (very patiently) to your solution.

Thanks again for your great posts.

Dudi
10/5/2008 6:39 PM | # re: IsolatedStorage as a Silverlight object cache
hi, a few days ago, I improved a package util class where I found at Silverlight Hand-on lab, in the pdf file named "SL2B2-HOL-AppPartitioning.pdf".

it meets some of the concerns that you mentioned.

anyone who is interested may download it here:

files.cnblogs.com/unruledboy/PackageUtil.zip

but due to the bug in SL2B2, the cache functionality will fail until the SL2 RTM as you mentioned.

10/5/2008 6:42 PM | # re: IsolatedStorage as a Silverlight object cache
usage:

private void OnDownloadClick(object sender, RoutedEventArgs e)
{
Result.DataContext = new string[] { "hello", "world" };
if (!packageLoaded)
{
Uri uri = new Uri("localhost:15388/.../SamplePackage.xap");
SLPackage package = new SLPackage(uri, SLPackage.CacheTypes.Site, 2);
package.PackageDownloaded += new SLPackage.PackageEventHandler(OnPackagePackageDownloaded);
package.LoadPackage();
}
}

private void OnPackagePackageDownloaded(object sender, SLPackage.PackageEventArgs e)
{
UIElement uc = PackageUtil.GetObject(e.PackageStream, "SamplePackage.dll", "SamplePackage.Hello");
Result.Children.Add(uc);

//Stream stream = PackageUtil.GetResource(e.PackageStream, "SamplePackage.dll", "application/binary");
//stream = PackageUtil.GetResource(e.PackageStream, "AppManifest.xaml", null);
}
10/24/2008 11:27 AM | # re: IsolatedStorage as a Silverlight object cache
Hi Tim,

1) Thanks for your efforts. I would like to ask is it possible to load access the client's Isolated Storage through ASP.NET from the server. Because, once the Silverlight application is loaded to users browser, it is efficient to store the whole xap file on some isolated storage, and when the user accesses the given domain, the aspx page hosting the Silverlight application can check whether the user previously accessed the web site, and if yes, the application will be downloaded from the isolated storage.

Once Microsoft introduced the concept of IsolatedStorage, it would be great to make its scope domain-wide rather than application-wide. It could solve many problems in exchanging xap files among silverlight applications located in the same domain. Also if the ASP.NET could have the access to that domain-wide IsolatedStorage, we could instantiate our Silverlight application from the client's isolated storage.

2) Another question, that I want to ask you is how it is possible to control the versions automatically in IsolatedStorage of a client. For example if we had DynamicXapDataGrid.xap and we updated it to newer one, how it is possible to check the version of previously downloaded xap file and replace it with the new one if it is old. Please, give some directions on that.

Thank you very much, Tim
10/24/2008 11:31 AM | # re: IsolatedStorage as a Silverlight object cache
Sarvar:

1. No that's not possible. Silverlight IsoStore is on the client...there would be no way for a server to reach into and end-user machine and grab that out...that'd be a risk :-).

The IsoStore is URI specific right now to protect the informaiton between applications.

2. Versioning is tricky in this type of technique as I mentioned...you'd have to manage some type of versioning flag yourself, perhaps using initParams.
10/24/2008 1:20 PM | # re: IsolatedStorage as a Silverlight object cache
Thank you for your immediate response, Tim.

How about to create a dummy silverlight app file which first checks if the main xap body is in Isolated Storage and if not downloads it from the server and caches it to IS. And when next time the user accesses the domain, the dummy application loads the body from local IS. Is it possible?
10/24/2008 1:24 PM | # re: IsolatedStorage as a Silverlight object cache
Dummy app is something like a master container which hosts all xap files and dynamically loads them when we need to switch another page, either from server or from local IS.
11/3/2008 12:43 AM | # IsolatedStorage cannot access file
Hi Tim,

I have used your code, but come up with an exception on reading files through store.OpenFile. It is not returning an instance of IsolatedStorageFileStream, so the NullReferenceObject exception is coming out. I don't know where could be the reason for that as I did everything right. I even tried to deploy your own project, but still ending with this exception. Can you hint me about this issue?
11/3/2008 5:39 AM | # re: IsolatedStorage as a Silverlight object cache
why IsolatedStorage used?
11/3/2008 8:39 AM | # re: IsolatedStorage as a Silverlight object cache
Micle: This is just an example.
11/5/2008 4:06 PM | # re: IsolatedStorage as a Silverlight object cache
Hi Tim,
I apolozise, the topic is irrelevent to this ISO storage but since you told that two silverlight apps don't share same ISO storage ,i have this option now.
the options i have are : either to redirect to a page where you can use this ISo storage by second app (or) show the output in a popup. please remember that my silverlight app is hosted inside a SilverPart.

I have a listbox width=400 and height=350 with image thumbnails as listitems, when user selects an Image and overlay should appear with images related to that item and i am getting from sharepoint lists.asmx webservice. It is working fine when you run in Visual studio. I have hosted this Silverlight App in a Silverpart Webpart (A generic webpart to host any silverlight app in sharepoint,it is from CODEPLEX). I have configured this silverPart to fit only the listbox.

Issue: when i click a listitem an overlay or popup is appeared within the sivlerpart with scrolls. i want to re-size the silverPart to fit the popup and shrink back to listbox size when you close popup.

Trick : use HTML Bridge but without javascript. Our company doesn't allow javascript in SPMasterpage.

I used HTMlPage.Window, but it is asking for Uri. i don't want to navigate to another page, just show images in a popup.

Thank you Tim.
12/2/2008 3:18 PM | # re: IsolatedStorage as a Silverlight object cache
Sarvar: this is possible as you suggest. Maintaining versioning is something you'd have to consider though.
1/3/2009 11:07 AM | # re: IsolatedStorage as a Silverlight object cache
This is a great idea and we are working on building a framework to do this. We will post it on codeplex when we are finished.
2/22/2009 4:32 PM | # re: IsolatedStorage as a Silverlight object cache
Hello Tim,
great article, thank you for your efforts.
I tried to implement loading .xap from isolated storage,
actually no problems, but combined with the http://www.codeplex.com/SilverlightLoader
I dont get the retrieved .xap file as the new source:
System.Windows.Browser.HtmlPage.Plugin.SetProperty("source", xapIsoSource);
It seems he does not accept the Uri-String when it was never downloaded from
there. Any help or hint would be welcome.
Regards Christoph
12/18/2009 12:22 PM | # IsolatedStorage
Hi Tim,

I have a .asx file in isolated storage. I need to provide it as a source to a silverlight 3 media element. I am not able to figure out how to do it. Can you help me out with this.
5/18/2010 2:09 AM | # re: IsolatedStorage as a Silverlight object cache
Hi,

I use SL4 and try to load an external assemblies. I follow your tutorial, but i already have a InternalMemoryStream in my openreadComplete's arg.
Does anyone know how to do?

Thanks in advance!
10/11/2010 1:00 PM | # re: IsolatedStorage as a Silverlight object cache
"To increase the quota, you must call this method from a user-initiated event, such as in an event handler for a button-click event."

Why???

Using isolated storage as caching mechanism is just great for big applications, but with this restriction it's all over.

If we already have a modal window asking for user permission, what's the point of this restriction?

 
Please add 6 and 3 and type the answer here:

DISCLAIMER:

The opinions/content expressed on this blog are provided "ASIS" with no warranties and are my own personal opinions/content (unless otherwise noted) and do not represent my employer's view in any way.