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

Auto-Updating TextBlock for Silverlight

This textblock usercontrol updates its contents every second. Just set a binding to the text property and voila!

Note that the part inside the Dispatcher BeginInvoke method that gets the binding expression and sets the binding to the parent binding of the binding expression is equivalent to the missing .UpdateTarget() found in WPF.

XAML

<UserControl x:Class="CLARIA.Infrastructure.SL.RichClient.Controls.UpdatingTextBlock"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
		mc:Ignorable="d"
		d:DesignHeight="28" d:DesignWidth="240">
	<TextBlock Name="text" />
</UserControl>

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Threading;
using System.Windows.Data;
using System.ComponentModel;

namespace CLARIA.Infrastructure.SL.RichClient.Controls
{
public partial class UpdatingTextBlock : UserControl, IDisposable
{
public UpdatingTextBlock()
{
InitializeComponent();

// Set timer
if (!DesignerProperties.GetIsInDesignMode(this))
{
t = new Timer(new TimerCallback(TimerCallbackMethod), null, 0, 1000);
}
}

Timer t;
BindingExpression exp;

public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}

// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc…
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(“Text”, typeof(string), typeof(UpdatingTextBlock), new PropertyMetadata(“”));

void TimerCallbackMethod(object target)
{
Dispatcher.BeginInvoke(() =>
{
if (exp == null)
{
exp = GetBindingExpression(UpdatingTextBlock.TextProperty);
}
if (exp != null)
{
SetBinding(UpdatingTextBlock.TextProperty, exp.ParentBinding);
}
text.Text = this.Text;
});
}

~UpdatingTextBlock()
{
Dispose();
}

#region IDisposable Members

bool _disposed = false;

public void Dispose()
{
if (!_disposed)
{
if (t != null)
t.Dispose();
_disposed = true;
}
}

#endregion
}
}

Uncategorized

Human-Friendly Dates


#region Human Friendly Dates

///

/// Takes instances of dates and converts them to more human-readable interpretations.
///

///
///
///
public static string ToHumanFriendlyDate(this DateTime date, bool neverShowSeconds = true)
{
TimeSpan ts = DateTime.Now.Subtract(date);
return ToHumanFriendlyDate(ts, neverShowSeconds);
}

///

/// Takes instances of dates and converts them to more human-readable interpretations.
///

///
///
///
public static string ToHumanFriendlyDate(this DateTime? date, bool neverShowSeconds = true)
{
if (date == null)
return null;
return ToHumanFriendlyDate(date.Value, neverShowSeconds);
}

///

/// Takes instances of dates and converts them to more human-readable interpretations.
///

///
///
///
public static string ToHumanFriendlyDate(this TimeSpan timespan, bool neverShowSeconds = true)
{
/* Examples of conversions:
* < 1m : Less than a minute [ago|to go]
* 1: A minute [ago|to go]
* 2: A couple minutes [ago|to go]
* 3-4: A few minutes [ago|to go]
* 5+: [min] minutes [ago|to go]
* 1 hour: An hour ago/to go
* 2 hours: A couple hours [ago/to go]
* Yesterday: Yesterday at 12:30 PM
* Tomorrow: Tomorrow at 12:30 pm
*/
bool isInFuture = timespan.TotalMinutes != Math.Abs(timespan.TotalMinutes);
Func endQualifier = () => isInFuture ? "to go" : "ago";
Func formatDateFunc = date => date.ToShortDateString().Replace("/" + DateTime.Now.Year, "");
if (Math.Abs(timespan.TotalSeconds) < 5)
{
return "A few seconds " + endQualifier();
}
else if (Math.Abs(timespan.TotalSeconds) < 60)
{
return "Less than a minute " + endQualifier();
}
else if (Math.Abs(timespan.TotalMinutes) < 3)
{
return "A couple minutes " + endQualifier();
}
else if (Math.Abs(timespan.TotalMinutes) < 5)
{
return "A few minutes " + endQualifier();
}
else if (Math.Abs(timespan.TotalMinutes) < 55)
{
return timespan.Minutes.ToString() + " minutes " + endQualifier();
}
else if (Math.Abs(timespan.TotalHours) < 1)
{
return "Less than an hour " + endQualifier();
}
else if (Math.Abs(timespan.TotalHours) < 2 && Math.Abs(timespan.Hours) == 1)
{
return "An hour " + endQualifier();
}
else if (Math.Abs(timespan.TotalHours) < 3)
{
return "A couple hours " + endQualifier();
}
else if (Math.Abs(timespan.TotalHours) < 5)
{
return "A few hours " + endQualifier();
}
else
{
string day;
DateTime d = DateTime.Now.Subtract(timespan);
if (Math.Abs(timespan.TotalDays) < 2)
{
if (d.Day DateTime.Now.Day)
{
day = "Tomorrow";
}
else
{
day = "Today";
}
}
else
{
day = formatDateFunc(d);
}
if (neverShowSeconds)
{
return day + ", " + d.ToString("h:mm tt").Replace(":00", "");
}
else
{
return day + ", " + d.ToShortTimeString().Replace(":00", "");
}
}
}

///

/// Takes instances of dates and converts them to more human-readable interpretations.
///

///
///
///
public static string ToHumanFriendlyDate(this TimeSpan? timespan, bool neverShowSeconds = true)
{
if (timespan == null)
return null;
return ToHumanFriendlyDate(timespan.Value, neverShowSeconds);
}

#endregion

Uncategorized

Get enums by index, get index of an enum value in an enum

I like using enums for simple selections. I feel it makes my code clean. Over time you may feel that parts of your program have “outgrown” enums and you may need to switch to classes to handle extra logic. You can, but with attributes, you do not have to. If a parameter must be one of a few choices, even if it’s nullable, then use an enum.

I’m writing an iPhone app using Monotouch where I am displaying enum options to be picked from a list. The functions I am listing below help greatly in dealing with indices on enums.

 

public static TEnum GetEnumByIndex(int index)
{
var etype = typeof(TEnum);
if (!etype.IsEnum)
throw new ArgumentException(“‘TEnum’ must be of an enum type.”);
var enums = Enum.GetValues(typeof(TEnum));
if (index > enums.Length)
throw new ArgumentOutOfRangeException(“index”);
TEnum option = (TEnum)enums.GetValue(index);
return option;
}

public static int? GetIndexOfEnumValue(TEnum value)
{
var etype = typeof(TEnum);
if (!etype.IsEnum)
throw new ArgumentException(“‘TEnum’ must be of an enum type.”);
var enums = Enum.GetValues(typeof(TEnum));
int i = 0;
foreach (object e in enums)
{
if (((TEnum)e).Equals(value))
return i;
i++;
}
return null;
}

In other news, my daughter is doing really well with her chemo treatments. Hopefully she will be cancer free in a few years! See my wife’s page detailing my daughter’s battle with Acute Lymphoblastic Leukemia.

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.

Silverlight, UI Design, XAML

Bind a list of items to an accordion control with grouping

My model looks like this:


public sealed class ReportItem
{

public string Name { get; set; }
public string Description { get; set; }
public string GroupName { get; set; }

}

I want to display these reports in an accordion control, where the items are grouped by the
GroupName property. The group name would be the accordion header buttons, and under each
accordion group would appear the items containing that group name.

You’ll need a CollectionViewSource:


<navigation.Page ... xmlns:data="clr-namespace:System.Windows.Data;assembly=System.Windows"

 

	<navigation:Page.Resources>
		<data:CollectionViewSource x:Key="ReportsViewSource" Source="{Binding Reports}">
			<data:CollectionViewSource.GroupDescriptions>
				<PropertyGroupDescription PropertyName="GroupName" />
			</data:CollectionViewSource.GroupDescriptions>
		</data:CollectionViewSource>
	</navigation:Page.Resources>

I’m using a model object so my report collection is a property called Reports.

The GroupName is the property I am grouping by.

		<toolkit:Accordion ItemsSource="{Binding BindsDirectlyToSource=True, Source={StaticResource ReportsViewSource}, Path=View.Groups}" Grid.Row="1" HorizontalAlignment="Left" Name="accordion1" Width="315" VerticalAlignment="Stretch">
			<toolkit:Accordion.ContentTemplate>
				<DataTemplate>
					<ListBox ItemsSource="{Binding Items}" BorderThickness="0"><!-- this is the Group.Items -->
						<ListBox.ItemTemplate>
							<DataTemplate>
								<TextBlock FontWeight="Bold" Text="{Binding Name}" /><!-- this is the Report.Name -->
							</DataTemplate>
						</ListBox.ItemTemplate>
					</ListBox>
				</DataTemplate>
			</toolkit:Accordion.ContentTemplate>
			<toolkit:Accordion.ItemTemplate>
				<DataTemplate>
					<TextBlock Text="{Binding Name}" /><!-- this is the Group.Name -->
				</DataTemplate>
			</toolkit:Accordion.ItemTemplate>
		</toolkit:Accordion>


ReportsViewSource is my CollectionViewSource. The property View contains some objects, like Items for the items without grouping.
The property Groups contains a collection of Groups, each with a Name and Items property. The accordion’s ItemTemplate defines the
headers, while the ContentTemplate defines the content to go into each accordion. Here I am using a ListBox to display the subitems.

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?

C#, Silverlight, XAML

Binding to Page Title in Silverlight

In SL4, the title of a page in a Silverlight navigation project is not implemented as a depenency property. You can’t bind the title of a page from a view model directly to it. While this article was a great starting point, the first 2 examples did not work for me.

I implemented a dependency property to handle it. Gets to the property may not work well, but its purpose is to set the page title, so that should not matter.

///

/// This allows a page title in a Silverlight Navigation application to be bound to.
/// Silverlight page titles are not DPs so they cannot be bound by normal means.
/// This replaces the event used in codebehind called OnDataLoaded, whose only
/// purpose was to set the page title.
/// Taken & Modified from http://forums.silverlight.net/forums/p/97849/223734.aspx
///

public class PageTitleBindingHelper : FrameworkElement //, INotifyPropertyChanged
{

public string PageTitle
{
get { return (string)GetValue(PageTitleProperty); }
set { SetValue(PageTitleProperty, value); }
}

// Using a DependencyProperty as the backing store for PageTitleProperty. This enables animation, styling, binding, etc...
public static DependencyProperty PageTitleProperty;

public PageTitleBindingHelper()
{
PageTitleProperty = DependencyProperty.Register("PageTitleProperty", typeof(string), typeof(PageTitleBindingHelper), new PropertyMetadata("", new PropertyChangedCallback(PageTitleChanged)));
}

private void PageTitleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
HtmlPage.Document.SetProperty("title", e.NewValue);

Page p = App.Window.ContentFrame.Content as Page;
if (p != null)
p.Title = (string)e.NewValue;

//NotifyPropertyChanged("PageTitle");
}
}

To use: Place an instance in your page resources and bind the PageTitle property to whatever property is on your view model.

EDIT: HtmlPage seemed to crash randomly for other users of my app, so I’ve changed this. I have a reference to my main window available from the App class, where I was using it to hide parts of the UI for a kiosk, etc. I use that and figure out what page I am on, and set the title that way. It may not be the cleanest solution, but I will revisit this once I have some spare time.

Uncategorized

FizzBuzz in one line C# Linq

The FizzBuzz problem is this:
– Print numbers from 1 to 100.
– Print “Fizz” instead of the number if the current number is evenly divisible by 3.
– Print “Buzz” instead of the number if the current number is evenly divisible by 5.
– Print “FizzBuzz” instead of the number if the current number is evenly divisible by both 3 and 5.

Here’s the FizzBuzz problem, expressed as a single line in C# using Linq:


Enumerable.Range(1, 100).ToList().ForEach(i => Console.WriteLine((i % 3 == 0 && i % 5 == 0) ? "FizzBuzz" : (i % 5 == 0) ? "Buzz" : (i % 3 == 0) ? "Fizz" : i.ToString()));

It took about 45 seconds to write the code. =]

Uncategorized

Run PowerShell scripts within Silverlight 4 OOB Elevated Permissions

You have to run your SL app with elevated permissions out-of-browser, but this will allow you to query Powershell. You’ll have to parse the output yourself.

namespace TestPowershellScripting
{
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices.Automation;

///

/// Powershell runner for Silverlight applications.
/// Requires that OOB and elevated permissions are active.
///

public class PowerShellSL
{

///

/// Name of the PowerShell executable.
///

private const string PowerShellExecutable = "powershell.exe";

///

/// Specifies whether to include user profile information as part of the PowerShell
/// executing context. When set to false, PowerShell runs faster, but does not have
/// access to user-level environment variables.
///

public bool UseProfile { get; set; }

///

/// Runs a PowerShell script and returns the output as a string.
/// In the PowerShell script, use WriteHost to write output to standard output.
/// The script is written to standard input and run using the WScript library.
///

/// The PowerShell script to run, expressed as a string.
///
/// A FileNotFoundException is thrown if Powershell cannot be run.
/// An InvalidOperationException is thrown if the automation factory is unavailable.
public string Run(string script) {

// Check to see if stuff is available
if (!AutomationFactory.IsAvailable)
throw new InvalidOperationException("AutomationFactory is unavailable. Program must be run as out-of-browser with elevated permissions.");

var shell = AutomationFactory.CreateObject("WScript.Shell");
string cmd = PowerShellExecutable + " -NonInteractive " + (UseProfile ? "" : "-NoProfile") + " -WindowStyle Hidden -Command -";
string output = "";

dynamic result = null;
try {
result = shell.Exec(cmd);
result.StdIn.Write(script);
result.StdIn.Close();

while (!result.StdOut.AtEndOfStream) {
output += result.StdOut.ReadLine() + "\r\n";
}
} catch (FileNotFoundException fnfex) {
throw new FileNotFoundException("PowerShell is not installed on the local computer.", fnfex);

}

return output;
}

///

/// Runs a PowerShell script asynchronously (using a BackgroundWorker) and returns the output as a string.
/// When results are available, the delegate 'results' is called with the program output.
/// In the PowerShell script, use WriteHost to write output to standard output.
/// The script is written to standard input and run using the WScript library.
///

/// The PowerShell script to run, expressed as a string.
/// The delegate method to call when results have been returned.
/// A FileNotFoundException is thrown if Powershell cannot be run.
/// An InvalidOperationException is thrown if the automation factory is unavailable.
public void RunAsync(string script, Action results) {
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(
(sender, e) => {
e.Result = this.Run((string)e.Argument);
}
);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
(sender, e) => {
if (results != null)
results((string)e.Result);
}
);
bw.RunWorkerAsync(script);
}

}

}