Uncategorized

IE9 and Silverlight caching problems

I ran into a problem with customers not able to retrieve a new copy of a silverlight app using IE. I took over a user’s session remotely to witness this myself. I pressed F5 and Ctrl-F5 (hard refresh) and while the HTML page hard-refreshed, the XAP file refused to load until I deleted the customer’s IE cache and reloaded it. Why IE refused to honor the hard refresh, I will never know; but one thing is for certain, and that is you can NEVER trust the caching policies of the browser completely.

I found this link http://codeblog.larsholm.net/2010/02/avoid-incorrect-caching-of-silverlight-xap-file/ describing how to fix this issue. In the OBJECT tag that hosts the SL app in your HTML page, you append to the end of the XAP file url the last file modification date/time. This way, the XAP file will only reload if the file changes. I had to make some modifications to the code I got from the aforementioned URL to make it work properly.

So, instead of:

<param name="source" value="ClientBin/SilverlightApp.xap"/>

You want to put in its place:

<%
string orgSourceValue = @"~/ClientBin/SilverlightApplication.xap"; // path to and name of your XAP file
string param;
if (System.Diagnostics.Debugger.IsAttached)
{
param = "<param name=\"source\" value=\"" + orgSourceValue + "\" />";
}
else
{
string xappath = HttpContext.Current.Server.MapPath(orgSourceValue);
DateTime xapCreationDate = System.IO.File.GetLastWriteTime(xappath);
param = "<param name=\"source\" value=\"" + orgSourceValue.TrimStart('~') + "?ignore=" + xapCreationDate.ToString("yyyymmddhhmmss") + "\" />";
}
Response.Write(param);
%>

This way, if you are debugging your application, the path remains the same, but if there is no debugger attached, then the file is cached as long as the file modification date does not change.

I made the following changes from the original poster’s code:

  • The output of the time was a simple .ToString() which did not form a valid URL. This has been changed into more of a timestamp, where all non-numeric values were removed.
  • How the path to the XAP file was stored has been changed. Since we’re working from the base dir out, I used the .Net style of server paths, prepending the tilde (~) and then when writing it out, removing the tilde from the start of the path.
I will also add that the “ignore?” portion can really be anything you want it to be. As long as the URL does not change, the application will be cached by browsers that are set up to support caching. Once you upload a new copy of your XAP file, the file modification date will change, and then the URL changes, thus forcing browsers to get a new copy of your app.

Hope this helps someone out there.

Uncategorized

Silverlight, popup windows, and browser/out of browser differences

Opening popup windows in Silverlight is a pain in the rear. However, a way has been found to work around this. Subclass the HyperlinkButton and expose a new method that calls the protected Click() method.


internal class OOBPopup : HyperlinkButton
{
public void Navigate(Uri url)
{
this.NavigateUri = url;
this.TargetName = "_blank";
base.OnClick();
}
}

To use:

new OOBPopup().Navigate(url);

Let’s compare this with the two other options, HtmlPage.Navigate() and HtmlPage.PopupWindow(). Everything below is on Windows 7 64-bit. (IB = in browser, OOB = out of browser)

IB IE 9: All three methods work.
IB Safari 5.1.1: Only the OOB popup works.
IB Firefox 7: Only the OOB popup works.
IB Chrome: Only the OOB popup works.
OOB: Only the OOB popup works.

Where a method does not work, either you will get a message saying that the HTML/DOM scripting bridge is disabled, or the browser (especially Safari) will just eat the popup and act like nothing happened.

With the OOBPopup class setting enableHtmlPopups in the OBJECT tag hosting the silverlight app is not required, so it’s a great way to get popup windows to work. The app I am working on makes heavy use of SSRS so popup windows are necessary.

Uncategorized

RIA Services and Timestamp/Concurrency Field Gotcha

I’m working with a table that has a timestamp column in EF. I’m using this to detect when multiple people are editing the same data so that one person’s changes don’t get overwritten by another user. In the EF designer, you would mark this property as StoreGeneratedPattern=Computed and ConcurrencyMode=Fixed, so that the code gen knows to generate a read-only field that gets roundtripped to the server on updates. If, during an update, the timestamp value that gets roundtripped to the server differs from the one in the database, then an OptimisticConcurrencyException is thrown so you have a chance to get the stored version of the data and possibly pop up a dialog that allows you to merge the changes together.

However, I have come across a gotcha when working with RIA services. Since the timestamp field is not NULLable, RIA services marks it as [Required] and [Editable(false)] when generating the code. When creating a new class to add to the data store, this ends up messing with validation, so in my datagrid it pops up with “Timestamp field is required”. You can’t set the field yourself, as it will throw an exception saying that the field is read-only.

A solution is found in the comments here for Linq-to-SQL, so here’s how to do it for EF.

I didn’t bother using the EF designer, I right-clicked the file in VS and clicked “Open With…”, then clicked XML editor. This brings up the XML schema that’s used to generate all of the code. Scroll down until you can find the class, mine was <EntityType Name="Class">. Find the property for your timestamp, mine was <PropertyType Name="Timestamp" ConcurrencyMode="Fixed" Nullable="false" ...>. Change that Nullable="false" to true, and save. The RIA services generated code will no longer require you to fill in the timestamp, and the database will handle creating the timestamp for you when the object is persisted to the database.

However, there is one more gotcha – it’s still marked as a required field. There may be a better way to accomplish this, but what I did was this: I have a field called CreationTimestamp, so I used the partial OnCreationTimestamp() method:

partial void OnCreationTimestampChanged()
{
if (Timestamp == null)
Timestamp = new byte[] { };
}

This fixes the issue, for now.

Anyone know of a better way?