Incredibly Useful Developer Tools
These are some tools I have a hard time living without.
// Dave
Step 1: Change the default behavior of WebRequest errors goes to the ClientHttp stack instead of the default browser handling.
// Tell Silverlight that the ClientHttp stack should be used. // This let's us get more detailed exceptions from our service vs. the default Browser handling of errors. (401 Not Found) // http://msdn.microsoft.com/en-us/library/system.net.webrequest.registerprefix.aspx // http://msdn.microsoft.com/en-us/library/dd920295(VS.95).aspx bool httpResult = WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp); bool httpsResult = WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
Step 2: Change the global exception handling in Silverlight so that the actual exception messages bubble up to a custom ChildWindow (in this case, it’s called ErrorWindow).
/// <summary> /// Occurs for all unhandled exceptions. Launches the /Views/ErrorWindow.xaml page /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) { // If the app is running outside of the debugger then report the exception using // the browser's exception mechanism. On IE this will display as a yellow alert // icon in the status bar and Firefox will display a script error. if (!Debugger.IsAttached) { // NOTE: This will allow the application to continue running after an exception has been thrown // but not handled. // For production applications this error handling should be replaced with something that will // report the error to the website and stop the application. e.Handled = true; //Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); }); var error = new ErrorWindow(e.ExceptionObject.InnerException.Message.ToString() + Environment.NewLine + e.ExceptionObject.InnerException.StackTrace); error.Show(); } }
Step 3: Update your web.config so that includeExceptionDetailsInFaults is true.
<system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="LLServiceBehavior"> <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> <serviceMetadata httpGetEnabled="true"/> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors>
Step 4: Test it by throwing an exception in your web service.
throw new ArithmeticException("Check me out!");
Hope this helps!
// Dave
Here is some clever code to Enable or Disable all buttons on a Silverlight page.
/// <summary> /// Bulk disable / enable all of the buttons on the UI /// </summary> /// <param name="isEnabled"></param> public void EnableDisableButtons(bool isEnabled) { var buttons = GetButtonControls(LayoutRoot).OfType<Button>(); foreach (var btn in buttons) { btn.IsEnabled = isEnabled; } } /// <summary> /// Return all button controls on the page /// </summary> /// <param name="root"></param> /// <returns></returns> private static IEnumerable<DependencyObject> GetButtonControls(DependencyObject root) { var doList = new List<DependencyObject> { root }; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++) doList.AddRange(GetButtonControls(VisualTreeHelper.GetChild(root, i))); return doList; }
This little tip is probably all over the place, but just in case:
When you open a xaml file, the Visual Studio 2010 designer is also loaded, which can take significant time.
If all you’re doing is messing with the xaml manually (rather than dragging things around the design surface), here is how you turn the designer off:
Tools | Options | Text Editor | XAML | Miscellaneous
John Stockton gave me that little tip, and it’s definitely sped things up.
Hope this helps!
// Dave
Often you’ll want to pass selected values from one page in a Silverlight 4 application to another.
Query Strings are a valid approach, but you might want to pass more than simple strings or IDs in the URL.
This quick walkthrough will show you to use access a simple static variable from one page, on another. This could easily be a full object, but in this example it will just be a string.
Start off by creating a new project. Silverlight now ships with a “Silverlight Navigation Application” template, which is useful for creating Line of Business (LOB) applications. There are also a number of other templates, such as the Cosmopolitan Theme that you might find useful.
Once the project is created, open Home.xaml.cs and add a static variable to Home.xaml.cs. This is the value we’ll be reading back on the next page.
public partial class Home : Page { public static string ValueFromHome = "A Value on the Home.xaml.cs page";
Now add a new Page, using Page1.xaml as the default name.
We need a way to navigate here, so add a Button to Home.xaml.
<navigation:Page x:Class="SLNavDemo.Home" 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" xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480" Title="Home" Style="{StaticResource PageStyle}"> <Grid x:Name="LayoutRoot"> <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}"> <StackPanel x:Name="ContentStackPanel"> <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}" Text="Home"/> <TextBlock x:Name="ContentText" Style="{StaticResource ContentTextStyle}" Text="Home page content"/> <Button Name="btnGoToPage1" Content="Go to Page 1" Click="btnGoToPage1_Click"/> </StackPanel> </ScrollViewer> </Grid> </navigation:Page>
Utilizing the NavigationService, add the following content to the Click Event. Notice the relative URI for /Page1. We don’t have to specify the full file name for the page, since the Navigation service can figure this out for us using UriMappings.
private void btnGoToPage1_Click(object sender, RoutedEventArgs e) { this.NavigationService.Navigate(new Uri("/Page1", UriKind.Relative)); }
If you’re curious how this works, you can view the UriMappings in MainPage.xaml. The last UriMapping simply says “if the page isn’t mapped explicitly, just try to find a file in the /Views folder with a .xaml extension.
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed"> <navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/> <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/> </uriMapper:UriMapper> </navigation:Frame.UriMapper> </navigation:Frame>
Now open Page1.xaml and add a simple TextBlock to the page.
<navigation:Page x:Class="SLNavDemo.Views.Page1" 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" xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" d:DesignWidth="640" d:DesignHeight="480" Title="Page1 Page"> <Grid x:Name="LayoutRoot"> <TextBlock Name="tb" /> </Grid> </navigation:Page>
Now all we have to do is update the Text property, with the value from the Home page.
public Page1()
{
InitializeComponent();
tb.Text = Home.ValueFromHome;
}
Hit F5 to run.
Click the button we added, and the application will navigate to Page1, with the text applied.
You’ll also notice that the URL changed to /Page1. This is to support the browsers back button.
A pretty simple example, but this can be powerful when passing along values from one page to another, for instance, selected items in a grid. You can also use this approach to reduce extra web service calls, if the values already exist on another page in the Silverlight application that the user has interacted with.
Hope this helps!
// Dave
Silverlight Resources
Defining Resources
In Silverlight you can define resources, which will be available to the rest of the application. You can define these resources at the top of your user controls, in your pages, App.xaml, or in separate ResourceDictionaries.
App.xaml is usually a good place to put the bulk of all style elements. For Silverlight, this includes not only fonts and colors, but also animations (storyboards), convertors, and the like.
It’s a good idea to use a single defined font throughout the application.
In this example it’s named “MainFont”, which is made of Tahoma, Verdana, and Arial. Just like CSS, if one font is unavailable, it moves to the next. Fortunately, all 3 are built-in Silverlight fonts.
<FontFamily x:Key="MainFont">Tahoma, Verdana, Arial</FontFamily>
The next piece to understand is the difference between “brushes” and “styles”.
A Brush, like this, represents the color gray.
<SolidColorBrush x:Key="GrayButtons" Color="#FF5C5A67" />
Now a Style is something that affects other controls throughout the application. Here is one that affects all TextBlocks:
<Style TargetType="TextBlock"> <Setter Property="FontFamily" Value="{StaticResource MainFont}" /> <Setter Property="Foreground" Value="{StaticResource GrayButtons}" /> <Setter Property="FontWeight" Value="Normal" /> <Setter Property="FontSize" Value="10.667" /> </Style>
You can read the above as saying “for any TextBlock control that does not have a specific styling specified, use MainFont and GrayButtons. Make the FontWeight Normal, and not Bold, and set the default FontSize to 10.667”.
Styles cascade in Silverlight, much like CSS. So the App.xaml is the first set of Styles to be defined. Controls within the application may override these styles to set properties like Font Weight or Font Size, but for the most part, this file includes the Styling for the application.
If you’re still with me, I’ll throw out just two more quick things:
- · When you style a Telerik control, the design tools automatically define every property available for that Telerik control, including animations (storyboards). This is a great way to end up with a ton of content in a single file…
- · If you download the trial version of Microsoft Expression Blend 4, you’ll be able to open and navigate the App.xaml file and actually see the styles.
Utilizing Resources
There are two basic ways to utilize the Resources you’ve defined.
The preferred way is to reference them directly in xaml like this:
<TextBlock Foreground="{StaticResource GrayButtons}" Text="Some Text" x:Name="txtSomeText" FontSize="10.667" FontFamily="{StaticResource MainFont}" />
This has the added benefit of supporting Designers, who will be likely be familiar with Expression Blend.
Another way is to reference them directly in code. For Resources stored in App.xaml, you would use the following syntax:
SolidColorBrush titleTextBrush = App.Current.Resources["GrayButtons"] as SolidColorBrush;
Hope this helps, let me know if you have any questions!
// Dave
Assembly assembly = Assembly.GetExecutingAssembly(); String version = assembly.FullName.Split(',')[1]; String fullversion = version.Split('=')[1]; int build = int.Parse(fullversion.Split('.')[2]); int revision = int.Parse(fullversion.Split('.')[3]); DateTime buildDate = new DateTime(2000, 1, 1).AddDays(build).AddSeconds(revision * 2); String fulldate = buildDate.ToLocalTime().ToString(CultureInfo.InvariantCulture); txtAssemblyVersion.Text = fullversion + Environment.NewLine + buildDate.ToShortDateString() + " " + buildDate.ToShortTimeString();
It’s just that easy!
// Dave
I love my Zune.
Srsly.
I often subscribe to conference videos (PDC, TechEd, MIX, TedTalks), and watch them over time with my Zune.
This year though, the TechEd folks decided to expose the sessions as an oData feed. I love oData and the possibilities it brings.
However, I can’t subscribe to an oData feed with the Zune desktop software, and expect it to work.
I took some inspiration from this post, and reformatted the linq query so I could subscribe to the TechEd videos:
http://naveensrinivasan.com/2010/06/15/using-tech-ed-odata-to-download-videos/
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Syndication; using System.ServiceModel.Web; using System.Text; using System.Data.Services.Client; namespace TechEdZuneFeed { /// <summary> /// Represents a Zune Feed /// </summary> public class ZuneFeed : IZuneFeed { public Sessions.ODataTENA10Entities context; private Uri svcUri = new Uri("http://odata.msteched.com/Sessions.svc/"); public SyndicationFeedFormatter CreateFeed() { context = new Sessions.ODataTENA10Entities(svcUri); var qry = (from s in context.Sessions where s.Code.StartsWith("DEV") && s.SessionType.StartsWith("Breakout Session") orderby s.Code select s).Take(50); DataServiceCollection<Sessions.Session> sessions = new DataServiceCollection<Sessions.Session>(qry); // Create a new Syndication Feed. SyndicationFeed feed = new SyndicationFeed("TechEd 2010 Sessions", "All TechEd 2010 Sessions", null); // Configuure our list of items List<SyndicationItem> items = new List<SyndicationItem>(); // Iterate through each Session, adding it to our SyndicationItem list foreach (var session in sessions) { // Create a new Syndication Item. SyndicationItem item = new SyndicationItem(session.Title, session.Abstract, null); long length = 0; // the size of the attachment, in bytes string type = "application/x-ms-wmv"; // or other valid content-type var uri = new Uri(@"http://ecn.channel9.msdn.com/o9/te/NorthAmerica/2010/wmv/" + session.Code + ".wmv"); item.Links.Add(SyndicationLink.CreateMediaEnclosureLink(uri, type, length)); items.Add(item); } feed.Items = items; // Return ATOM or RSS based on query string // rss -> http://localhost:8732/Design_Time_Addresses/TechEdZuneFeed/Feed1/ // atom -> http://localhost:8732/Design_Time_Addresses/TechEdZuneFeed/Feed1/?format=atom string query = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"]; SyndicationFeedFormatter formatter = null; if (query == "atom") { formatter = new Atom10FeedFormatter(feed); } else { formatter = new Rss20FeedFormatter(feed); } return formatter; } } }
- The oData feed that’s exposed only shows 25 records at a time, so the feed above only allows you to subscribe to the top few records. I’ll leave changing this as an exercise to the reader (hint, you just need a for loop and some .Take(x) and .Skip(x))
- It doesn’t appear that every video has been released in wmv format yet, or my queries are wrong.
Hope this helps someone!
// Dave
Here is a simple example.
We have Categories, which have many Initiatives. Easy one-to-many relationship.
We have a Linq to SQL query, that grabs the Categories. Since the table relationship is in the Linq to SQL DataContext (.dbml file), it knows there is a relationship to Initiatives.
A little farther down in the code, we iterate through each of them.
However, this seemingly trivial line of code yields 480 queries!!
Linq to SQL uses “Deferred Loading”, which means it doesn’t attempt to get any data until the moment you need it. Unfortunately, it means in our foreach loop, we’re slamming the Initiatives table for every Category record.
We need to tell the DataContext ahead of time, that it needs to grab the Initiatives records in bulk any time the Category table is accessed.
Now it’s a single query against the database. This resultant query could be further tightened up, but for our purposes, it is less database calls, and better performance (1.1 seconds vs. 7.2 seconds).
DataLoadOptions can be a powerful technique for saving database round trips.
Hope this helps!
// Dave
As we all know, Ctrl+Shift+B is the keyboard shortcut to “Build Solution”.
However, when you are building a very large web app, it can take several minutes (or more) to build everything.
This is counter-intuitive when you’re working on a single page.
I did some quick research and found this:
Basically, you can assign a keyboard shortcut to Build.BuildPage, which will build the current page in your designer.
The result is a much shorter build process. It still builds the dependent projects, and copies the assemblies into the /bin directory.
—— Build started: Project: C:\…\WebApp\, Configuration: Debug Any CPU ——
Validating Page ~/Modules/Repository/Step1.aspx
Page Validation Complete
========== Build: 4 succeeded or up-to-date, 0 failed, 0 skipped ==========
Hope this helps!
// Dave
