Silverlight Help


Go Back
  • OpenID User Control in Silverlight – Part 1 UI Design

    More and more I see sites supporting OpenID as Authentication mechanism. I’m for example a user of sites like: I need to read this, Get Satisfaction and Google Login more or less.

    To support my own family I set up OpenID on my own domain, http://openid.mymonster.nl/ hosted by MyOpenID. This just works like a charm. For the purpose of this article I created a test identity at my MyOpenID. I suggest everyone doing development for openid connectivity to create a test identity, I don’t want to mess with my real OpenID identity. This is part one of a three part series on the creation of an OpenID User Control. I initially created the control for use in my own application and have submitted it to the Silverlight Control Builder Contest of 2009.

    UI Design

    One of the first things I was thinking about, was my design capacities. I came up with the following design, before signing in.

    OpenID User Control before signing in

    After you have signed in.

    OpenID User Control after signing in

    Yes I know, it’s very straightforward, and all the designers in this world could have thought about a better alternative. That’s why I thought this control to require the ability to template it. There are some articles on the web about templating Silverlight User Controls, this one helped me a lot.

    To support Templating for a user control it needs to inherit ContentControl

    Visually I identified two states:

    - Unauthenticated – This state is active when the user hasn’t signed in yet.

    - Authenticated – This state is active when the user has successfully signed in.

    If the sign in was successful the control will move to the authenticated state. These Visual States can be used in the Xaml part of the user control, which we will do later on.

    As you can see in the above pictures we can think about three essential parts in this control.

    - LoginButton, typeof(Button)

    - IdentityInput, typeof(TextBox)

    - IdentitySuccessLabel, typeof(TextBlock)

    When you combine just these parts the C# file will look like this. (Please note, some parts are left out for the clear picture).

     

    [TemplatePart(Name = LoginButton, Type = typeof(Button))]
    [TemplatePart(Name = IdentityInput, Type = typeof(TextBox))]
    [TemplatePart(Name = IdentitySuccessLabel, Type = typeof(TextBlock))]
    [TemplateVisualState(Name = VisualStates.Unauthenticated, GroupName = VisualStates.CommonStates)]
    [TemplateVisualState(Name = VisualStates.Authenticated, GroupName = VisualStates.CommonStates)]
    public class OpenIdLoginControl : ContentControl
    {
        private const string IdentityInput = "IdentityInput";
        private const string IdentitySuccessLabel = "IdentitySuccessLabel";
        private const string LoginButton = "LoginButton";
    
        private TextBox m_identityInput;
        private TextBlock m_identitySuccessLabel;
        private Button m_loginButton;
        
        public OpenIdLoginControl()
        {
            DefaultStyleKey = typeof(OpenIdLoginControl);
        }
    
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
    
            m_identityInput = (TextBox)GetTemplateChild(IdentityInput);
            m_loginButton = (Button)GetTemplateChild(LoginButton);
            m_identitySuccessLabel = (TextBlock)GetTemplateChild(IdentitySuccessLabel);
        }
    
        #region Nested type: VisualStates
    
        private static class VisualStates
        {
            internal const string Authenticated = "Authenticated";
            internal const string CommonStates = "CommonStates";
            internal const string Unauthenticated = "Unauthenticated";
        }
    
        #endregion
    }

     

    image To give this control a default style there needs to be a Themes directory and a xaml-file called Generic.xaml inside the library which will contain this control. The Generic.xaml file is a ResourceDictionary, and in this case we add a style for the OpenIdLoginControl. The xaml file kind of looks like the following (I removed the VisualStateManager parts for the VisualStates).

    <ResourceDictionary 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
      xmlns:openid="clr-namespace:MM.OpenId.Controls">
        <Style TargetType="openid:OpenIdLoginControl">
            <Setter Property="Width" Value="330" />
            <Setter Property="Height" Value="50" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="openid:OpenIdLoginControl">
                        <Border BorderBrush="Black" CornerRadius="4" BorderThickness="1">
                            <Grid x:Name="LayoutRoot" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
                                <Grid.RowDefinitions>
                                    <RowDefinition />
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="auto" />
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="auto"/>
                                </Grid.ColumnDefinitions>
                                <Image Grid.Column="0" Grid.Row="0" Source="/MM.OpenId.Controls;component/openid-icon.png" Width="30" Height="30" Margin="8"/>
                                <TextBlock Grid.Column="1" Grid.Row="0" x:Name="IdentitySuccessLabel" VerticalAlignment="Center" Margin="8" HorizontalAlignment="Center" />
                                <TextBox Grid.Column="1" Grid.Row="0" x:Name="IdentityInput" Text="http://openid.mymonster.nl/demo" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="8" />
                                <Button Grid.Column="2" Grid.Row="0" x:Name="LoginButton" Content="Sign In" Margin="8" />
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

    Because this is just the default template, you can change this to adjust it to your own application.

    Because the extreme size of this article I’ve split this article into multiple articles. I will provide the full source at the end of the last article. You can expect at least the following two parts:

    - OpenID Integration

    - Integration in your own application

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (0)

  • Tracking Silverlight (1, 2 and 3) support in Google Analytics

    Half a year ago I blogged about tracking Silverlight support in Google Analytics. I’ve had a lot of reactions on how to track Silverlight support. In my original article I made use of virtual page views to track Silverlight support. But there are different options available to track Silverlight support.

    Tracking through Virtual Page Views

    When you make use of Virtual Page Views to track Silverlight support you will get inconsistencies in your Page Views. You will get twice as much Page Views as there really are, not the best solution I think.

    Tracking through Events

    We can also track information in Google Analytics using Events. But it’s difficult to associate this information directly to the amount of visitors. I think using Events for tracking Silverlight support is the second best option we have.

    Tracking through User Defined Value

    We can also track information using the User Defined Value. This value is directly associated to the visitor. So even if your visitor takes a look at 10 pages, it will only track this value once. But the difficult thing with Google Analytics is the amount of User Defined Values we can have. It’s exactly one. So if you’re already using this User Defined Value for an other purpose, you will have to take advantage of the other options, like Event Tracking or Virtual Page View Tracking.

    My preferred way of Tracking Silverlight support is using User Defined Values. If you’re already using the User Defined Value, your best option for tracking is Events.

    Implementing tracking using the User Defined Value

    First of all, you’ll need the Silverlight.js file from MSDN Code. This file hasn’t changed for the release of Silverlight 3, so if you’ve already got this file, you don’t have to update it.

    Reference Silverlight.js:

    <script type="text/javascript" src="**/Silverlight.js"></script> 
    

    You probably already have the Javascript code for Referencing Google Analytics, it’s like this.

    <script type="text/javascript"> 
    	var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
    	document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
    </script>

    Let’s write a Javascript function to return the current Silverlight version. Although there’s backwards compatibility I only want to know the highest version supported.

    function getSilverlightVersion() {
    	var version = '';
    	var container = null;
    
    	try {
    		var control = null;
    		if (window.ActiveXObject) {
    			control = new ActiveXObject('AgControl.AgControl');
    		}
    		else {
    			if (navigator.plugins['Silverlight Plug-In']) {
    				container = document.createElement('div');
    				document.body.appendChild(container);
    				container.innerHTML= '<embed type="application/x-silverlight" src="data:," />';
    				control = container.childNodes[0];
    			}
    		}
    		if (control) {
    			if (control.isVersionSupported('3.0')) { version = 'Silverlight/3.0'; }
    			else if (control.isVersionSupported('2.0')) { version = 'Silverlight/2.0'; }
    			else if (control.isVersionSupported('1.0')) { version = 'Silverlight/1.0'; }
    		}
    	}
    	catch (e) { }
    
    	if (container) {
    		document.body.removeChild(container);
    	}
    	return version;
    }
    

    Let’s combine this function with the tracking of the value returned in the User Defined Value.

    var pageTracker = _gat._getTracker("UA-xxxx-x");
    $(function() {
    	pageTracker._trackPageview();
    	var version = getSilverlightVersion();
    	if (version) { pageTracker._setVar(version); }
    });

    Because I’m using JQuery on my homepage as well, I put this tracking in the ready of page loading.

     

    What does it look like in Google Analytics?

    Let’s take a look at the statistics for my website containing the User Defined Value since I added support for Silverlight 3 tracking. It’s interesting to see that already a lot of people are using Silverlight 3.

    Silverlight usage stats

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (1)

  • Silverlight 3 talking to a RIA Service, everything hosted on Windows Azure

    This is my first tryout of Windows Azure. I wanted to know if it’s possible to run the Preview of RIA Service on the CTP of Windows Azure.

    First of all you’ll need a lot of different components, and if you want to host your solution in the cloud you’ll also need to register at Windows Azure.

    - Register first: http://www.microsoft.com/azure/register.mspx

    I assume you’ve already a machine running Visual Studio 2008 SP1. But even then you’ll need to download and install the following components if you haven’t done yet.

    - Silverlight 3 Beta Tools for Visual Studio: http://www.microsoft.com/downloads/details.aspx?FamilyId=11dc7151-dbd6-4e39-878f-5081863cbb5d&displaylang=en

    - .NET RIA Service May 2009 Preview: http://www.microsoft.com/downloads/details.aspx?FamilyID=76bb3a07-3846-4564-b0c3-27972bcaabce&displaylang=en

    - Windows Azure Tools for Visual Studio May 2009 CTP: http://www.microsoft.com/downloads/details.aspx?FamilyID=11b451c4-7a7b-4537-a769-e1d157bad8c6&displaylang=en

     

    Everything installed? Let’s start with the general setup

    I first created an empty Solution, I always do this, because this is the only to have complete control over the naming. After that I added a Web Cloud Service, just like the screen below.

    Add a Web Cloud Service

    My solution still basic, no coding done yet, looks like this:

    Solution view with Web Project and Web Cloud Project

    Add a Silverlight Application project, and ensure that this Silverlight Application is hosted in the Webproject created in the previous step. Normally we would create a Silverlight Business Application, but we are going to associate RIA manually to this Silverlight Application.

    New Silverlight application dialog

    Let’s add a reference to the RIA library that’s needed to run this solution. You can find this library in: C:\Program Files\Microsoft SDKs\RIA Services\v1.0\Libraries\Silverlight\ (of course this can be different, depending on your installation location).

    - System.Windows.RIA

    There’s also a different library containing User Interface parts, we aren’t using it in this example but this is:

    - System.Windows.Ria.Controls

    Enable RIA on this Web Project

    Before we start our coding, let’s configure the web project to enable RIA Services. I’ve explained it before, but for Windows Azure it’s a little bit different.

    Add references to the following dll’s:

    - System.ComponentModel.DataAnnotations

    - System.Web.DomainServices

    - System.Web.Ria

    We still need to add the Http Handler for RIA Services. In my previous explanation I only explained how to do this for older versions of IIS. But Windows Azure is running on IIS 7. So the handler needs to be added to the handlers section in the system.webserver element.

    <add name="DataService" verb="GET,POST" path="DataService.axd" type="System.Web.Ria.DataServiceFactory, System.Web.Ria, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

     

    Enable RIA on the Web Cloud Project

    To enable RIA on the Web Cloud Project you have to change the ServiceDefinition.csdef file. Set the enableNativeCodeExecution to true.

    <?xml version="1.0" encoding="utf-8"?>
    <ServiceDefinition name="MM.SilverToGold.Web.Cloud" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
      <WebRole name="WebRole" enableNativeCodeExecution="true">
        <InputEndpoints>
          <!-- Must use port 80 for http and port 443 for https when running in the cloud -->
          <InputEndpoint name="HttpIn" protocol="http" port="80" />
        </InputEndpoints>
      </WebRole>
    </ServiceDefinition>

     

    Coding

    Now it’s time for coding, though I won’t explain much about the code. Please read my other RIA articles to get coding for RIA Service in your fingers.

    First the RIA Service part, a very simple HelloWorldDomainService, just add the following class to your Web project.

    using System.Web.DomainServices;
    using System.Web.Ria;
    
    namespace MM.HelloWorld.Web
    {
    	[EnableClientAccess]
    	public class HelloWorldDomainService : DomainService
    	{
    		[ServiceOperation]
    		public string HelloWorld(string name)
    		{
    			return string.Format("Hello {0}, from RIA Services running in the Cloud.", name);
    		}
    	}
    }

    When you now build the solution the Silverlight part will get generated.

    Show the RIA Services generated file 

    Coding the Silverlight part

    We now have completed the RIA Services part, let’s do the Silverlight coding. First some UI, containing a button, a input field and a result field. I also added a Click event to the button.

    <UserControl x:Class="MM.HelloWorld.Sui.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Width="400" Height="300">
        <Grid x:Name="LayoutRoot" Background="White">
            <StackPanel>
                <StackPanel Height="45" Width="Auto" Orientation="Horizontal">
                    <TextBlock FontSize="20" VerticalAlignment="Center" Width="201" Text="What's your name:"/>
                    <TextBox x:Name="NameTextBox" Height="25" Width="192" Background="#FFFFFFFF" BorderThickness="2,2,2,2" FontSize="14"/>
                </StackPanel>
                <Button Content="Hello?" Click="Button_Click" Height="33" FontSize="20" Margin="5,5,5,5"/>
                <TextBlock x:Name="WorldResponse"/>
            </StackPanel>
        </Grid>
    </UserControl>
    

    Alright, in the Click event we want to call the RIA Service, and let’s hope the RIA Service running in the cloud answers us.

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var helloWorldDomainContext = new HelloWorldDomainContext();
        helloWorldDomainContext.HelloWorldCompleted +=
            HelloWorldDomainContextHelloWorldCompleted;
        helloWorldDomainContext.HelloWorld(NameTextBox.Text);
    }

    The code for the HelloWorldCompleted event is very simple, it’s just setting the text of a TextBlock. Please note the event automatically get’s you back to the UI-Thread, no manually checking for UI-Thread required.

    private void HelloWorldDomainContextHelloWorldCompleted(object sender, InvokeEventArgs e)
    {
        if(e.ReturnValue!=null)
            WorldResponse.Text = e.ReturnValue.ToString();
    }

    When you test this locally, it will run, but the interesting part of course: Will it run on Windows Azure?

    Let it run on Windows Azure

    To get your solution to Windows Azure you will first have to right-click the cloud-project, and choose for Publish. This will open a Windows Explorer window showing the files that are ready for publishing. In this example it looks like this.

    image

    We’ll now logon to the Windows Azure portal. In here you have to create a Hosted Services project. I’ve already done this before, but there are articles on the web about creating a Hosted Services project on Windows Azure. To be honest, you can even do this without an article. My portal looks like this.

    Windows Azure Developer Portal

    Let’s choose the Hosted Services project, this project is called “Monster Cloud” in my Azure environment. But you can have your own name. The next page looks like this.

    Windows Azure Developer Portal - Hosted Services project

    Let’s click the Deploy button, and see what happens.

    Windows Azure Developer Portal - Deploy a new package screen

    Ah we need to provide a few things. An application package, we have this already, it’s in our publish folder that was created for us: MM.HelloWorld.Web.Cloud.cspkg.

    Let’s browse for this file, and also pick the Configuration Settings file, this is: ServiceConfiguration.cscfg.

    All that’s left is providing a label name, and clicking the Deploy button. My screen looked like this before clicking Deploy.

    Windows Azure Developer Portal - Deploy a new package screen, filled

    When clicking deploy a lot of things are being done behind the scenes. First of all the files are uploaded, but also a kind of VM get’s created for us, this takes some time, might even take 5 minutes or longer. So you definitely have to have a bit of patience. After the deployment was complete my screen looked like this.

    Windows Azure Developer Portal - Run the Azure package

    Let’s click the Run button.

    So let’s try the url that’s created for our staging environment. For a brief period of time, you can take a look at my staging environment.

    It’s working as expected. We now have a Silverlight 3 application communicating with RIA Services, and everything running in the cloud.

    Testing the Silverlight 3 communicating with RIA Services, all running on the cloud.

    You can download the Source Files. If you have any questions or remarks please let me know.

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (1)

  • Moonlight 2 Preview 4, testing differently

    I read we now have a new preview of the Moonlight runtime. It’s already the fourth preview. I’m giving a test run, just like I gave preview 1 and preview 3 a test run. But this time I did it differently. For the release notes see this page.

    What size is the Moonlight runtime actually?

    Currently it's about 9 MiB, what does this mean? That’s this runtime is small as well. That this size is about 50% higher than the Silverlight 3 beta release which has a size of about 6 MiB.

    Hunger for resources?

    Something else I wanted to see is the resources it’s using. Let’s see what’s happening to the CPU usage when running the Silverlight Toolkit example page? If it keeps running of course. But as the screenshot shows the CPU usage of Firefox the parent process for Moonlight is consuming about 50% of the CPU. When running for a period it keeps consuming around 50% to 80% of the CPU.

    Moonlight running the Silverlight Toolkit example page.

    Let’s compare the same page to Silverlight, running in Chrome, you will have to search the correct process, because each Chrome Tab is running in it’s own process. But then again it’s still not consuming as much CPU as Moonlight is. It’s around 2% CPU usage.

    Silverlight running the Silverlight Toolkit example page.

    I would say, besides a lot of problems, the moonlight team definitely will have to work on the performance, because you can expect a little bit of difference in performance but this isn’t what I would expect.

    Bugs?

    In the last preview the Silverlight Toolkit wasn’t running the charts. It is running it now, isn’t it? Well, for a small period of time it was running, but while writing some parts of this post it stopped running.

    image

    Is it running my own website? Of course for me is this very important because I now have a 3D-TagCloud running in Silverlight.

    image

    It’s getting better, but still a lot of work. But I like the approach the Moonlight team has while developing the new Moonlight version. It’s more public, compared to Silverlight 3 which only got one beta, and I don’t expect more to arrive because the RTW of Silverlight 3 is announced to be on July 10th.

    Do you have Silverlight sites I need to test drive during the next test drive?

    Full story

    Comments (1)

  • Moonlight Preview 3, a new Test Run.

    Moonlight has a new update. Basically I even missed the second preview. But here the test run of preview 3. The release notes are available here.

    I just had to search for updates on my add-ons. Just updated it like any other Firefox add on. And a restart of the browser was all that was needed.

    image

    Just giving it the same new try as before.

    Of course I start with testing the Silverlight.net site. Does it still work? Yes it does, it even feels a little better. Faster response. But looks are the same, so no new screenshot.

    Next is the Silverlight Toolkit Example page for Silverlight 2. Ah my first surprise. It runs, but let’s look at the charts, the browser stops responding.

    image

    What about the Silverlight Toolkit examples for Silverlight 3? Still crashes my dear browser.

    image

    What about the performance of the Hard Rock deep-zoom site? Yes it feels like it’s better, but I didn’t measure it. But while clicking around a little bit I got this screen and error.

    image 

    Hmm something else that is interesting to test is the 3D Tag Cloud by Peter Gerritsen will it run? And yes it runs! Interesting.

    image

    Hmm, in the end it still not enough for pre-production. But it performs better, at least it feels like it.

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (0)

  • Silverlight 3 and RIA Service – Cross Domain Proxy Enhanced

    Some time ago I wrote a very small RIA Service that could be used to overcome Cross Domain issues. It’s a proxy that sits between your Silverlight application and any server where you want to get content over http.

    A lot people know that not even half of the public API’s have cross domain configurations in place. The best solution that’s left is a proxy that can forward the calls to the public API and return the response back to the original caller. This article will expand the Silverlight part of the Cross Domain Proxy. Nothing needs to be changed to the RIA Service part of this Cross Domain Proxy.

    What is the problem we have with the solution from my earlier article about Cross Domain Proxy?

    One thing: The Cross Domain Proxy RIA Service is a generic service that can be used for all cross domain accesses. So basically we could write an application that’s performing multiple calls through the cross domain proxy at the same time. But how do we use the responses? We basically have one event “ProcessCompleted”, should we then react to this event by doing different things? Yes that could be, but we could do it differently as well.

    I’m thinking about a ProxyService that’s running on Silverlight which has a little bit more knowledge compared to the RIA Services generated stub. This service would enable to do a cross domain request but provide a function to be called on completion at the same time.

    The code using this service would look like this (firing multiple cross domain requests at the same time):

    var domainProxyService = new DomainProxyService();
    domainProxyService.Process(
        new ProxyRequest
            {
                Url = "http://www.silverlight.net/"
            },
        response => Debug.WriteLine(string.Format("1:", response.Content)));
    domainProxyService.Process(
        new ProxyRequest
            {
                Url = "http://www.asp.net/"
            },
        response => Debug.WriteLine(string.Format("2:", response.Content)));
    
    domainProxyService.Process(
        new ProxyRequest
            {
                Url = "http://www.azure.net/"
            },
        response => Debug.WriteLine(string.Format("3:", response.Content)));

    The DomainProxyService needs to be intelligent enough to be able to route the reponses to the correct function pointers.

    So there needs to be a key that can help associate the request with the response. In my last article I introduced the property Id in the ProxyRequest and the RequestId in the ProxyResponse. But for all purpose I don’t want to put a value in the Id, as a caller I don’t use this field, it’s just for internal usage so that association between Request and Response is possible.

    My idea is to put a dictionary in the DomainProxyService to associate the request id with a function pointer. I want the function point to be of a type void that accepts one parameter of type ProxyResponse. That would make a dictionary like this:

    private readonly Dictionary<Guid, Action<ProxyResponse>> m_dictionaryOfListeners =
        new Dictionary<Guid, Action<ProxyResponse>>();

    The method to process a request isn’t that difficult. It will generate a new Id if it’s still empty. Besides that it will add an item to the dictionary, the key with the associated function to call on completion. And after all the most important part, we will call the original RIA Service.

    public void Process(ProxyRequest proxyRequest, Action<ProxyResponse> callBack)
    {
        //Generate a unique id if it doesn't contain an id yet.
        if (Guid.Empty == proxyRequest.Id)
            proxyRequest.Id = Guid.NewGuid();
        //Add the callback to list of listeners for use on completion.
        m_dictionaryOfListeners.Add(proxyRequest.Id, callBack);
    
        m_domainProxy.Process(proxyRequest);
    }
    

    We will still need to have one generic listener for the ProcessCompleted event. This listener will look up the appropriate function to call in the dictionary. Will invoke the function and remove the item in the dictionary.

    private void DomainProxyProcessCompleted(object sender, InvokeEventArgs e)
    {
        var proxyResponse = e.ReturnValue as ProxyResponse;
        if (proxyResponse != null)
        {
            //If the dictionary contains listeners for this request
            if (m_dictionaryOfListeners.ContainsKey(proxyResponse.RequestId))
            {
                m_dictionaryOfListeners[proxyResponse.RequestId].Invoke(proxyResponse);
                m_dictionaryOfListeners.Remove(proxyResponse.RequestId);
            }
        }
    }
    

    To be complete here the full source of the DomainProxyService class. Please remember this article continues on my previous article about Domain Proxy.

    public class DomainProxyService
    {
        private readonly Dictionary<Guid, Action<ProxyResponse>> m_dictionaryOfListeners =
            new Dictionary<Guid, Action<ProxyResponse>>();
    
        private readonly DomainProxy m_domainProxy = new DomainProxy();
    
        public DomainProxyService()
        {
            m_domainProxy.ProcessCompleted += DomainProxyProcessCompleted;
        }
    
        private void DomainProxyProcessCompleted(object sender, InvokeEventArgs e)
        {
            var proxyResponse = e.ReturnValue as ProxyResponse;
            if (proxyResponse != null)
            {
                //If the dictionary contains listeners for this request
                if (m_dictionaryOfListeners.ContainsKey(proxyResponse.RequestId))
                {
                    m_dictionaryOfListeners[proxyResponse.RequestId].Invoke(proxyResponse);
                    m_dictionaryOfListeners.Remove(proxyResponse.RequestId);
                }
            }
        }
    
        public void Process(ProxyRequest proxyRequest, Action<ProxyResponse> callBack)
        {
            //Generate a unique id if it doesn't contain an id yet.
            if (Guid.Empty == proxyRequest.Id)
                proxyRequest.Id = Guid.NewGuid();
            //Add the callback to list of listeners for use on completion.
            m_dictionaryOfListeners.Add(proxyRequest.Id, callBack);
    
            m_domainProxy.Process(proxyRequest);
        }
    }


    Ps. This article is cross posted on:
    Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (409)

  • Silverlight on Linux – Running Moonlight 2.0 Preview 1

    Just a few days ago I heard about the Preview 1 version of Moonlight 2.0. Moonlight is the Open Source version of Silverlight that is able to run on Linux. My first reaction was I want to see this, and as soon as possible. So I downloaded the latest Ubuntu CD’s and started installing it on a Virtual PC. An article meant for installing an older version of Ubuntu under Virtual PC helped me.

    After that it was very easy just go to the Moonlight 2.0 site and install the Firefox plugin as you would install any other Firefox plugin.

    First test: Does it run the menu on Silverlight.net? Yes it does.

    Silverlight.net's Silverlight menu is working fine in Moonlight.

    Second test: Does it run the Silverlight Toolkit samples for Silverlight 2? No it doesn’t.

    Silverlight Toolkit for Silverlight 2 crashes Moonlight.

    Third test: Does it run the Hard Rock deep-zoom site? Yes it does.

    Hard Rock deep-zoom site half loaded in Moonlight.

    Though loading the data is very slow. I’m not sure if that’s because of my network, my vm or Moonlight. After a while this is the new screen.

    Hard Rock deep-zoom site fully loaded in Moonlight.

    Because the release notes mentions that some features that are part of Silverlight 3 are already supported. Fourth test: Does it run the Silverlight Toolkit samples for Silverlight 3? No it doesn’t. My whole browser crashed. It was just gone…

    Silverlight Toolkit for Silverlight 3 crashes Moonlight and Firefox.

    Alright what did I expect? ?Not much, but it was an interesting test-drive. And for the future I now have a Linux Virtual PC.

    Let’s hope for more stable Previews of Moonlight.

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (1)

  • Silverlight 3 and RIA Service – Creating a Proxy for Cross Domain HttpRequests

    One of the first things I thought about after writing my first two articles on RIA Services (Basic and Advanced) was how I could take advantage of this new technology.

    The first thing I could thing of was a solution that helps overcoming the Cross Domain Issues when accessing resources on the internet. What about something like a proxy service to overcome cross domain issues? Access our known proxy server which will forward the request to the actual resource on the internet.

    image

    Let’s first define the interface to communicate through. A very simple ProxyRequest that contains the resource to access on the ‘Any resource’ side, and an Id to make sure we can correctly associate the Request and the Response later on.

    namespace MM.Ria.CrossDomainProxy
    {
        public class ProxyRequest
        {
            [Key]
            public Guid Id { get; set; }
    
            public string Url { get; set; }
        }
    }

    The ProxyResponse is very simple as well. Almost the same as the ProxyRequest a RequestId associated with the Id in the ProxyRequest, the Content and of course something to store any error that occurred.

    namespace MM.Ria.CrossDomainProxy
    {
        public class ProxyResponse
        {
            [Key]
            public Guid RequestId { get; set; }
    
            public string Content { get; set; }
    
            public string Error { get; set; }
        }
    }

    And we need an Interface with an Operation, let’s call it process.

    namespace MM.Ria.CrossDomainProxy
    {
        public interface IProxy
        {
            ProxyResponse Process(ProxyRequest request);
        }
    }

    We now create the RIA Service implementation, by just delegating everything to a RegularProxy implementation. So this one is simple as well.

    namespace MM.Ria.CrossDomainProxy
    {
        [EnableClientAccess]
        public class DomainProxy : DomainService, IProxy
        {
            private readonly RegularProxy m_proxy = new RegularProxy();
    
            [ServiceOperation]
            public ProxyResponse Process(ProxyRequest request)
            {
                return m_proxy.Process(request);
            }
        }
    }

    And the magic is in the RegularProxy, don’t think we should call it magic though. Just an usage of the WebClient class to download the data from the Url.

    namespace MM.Ria.CrossDomainProxy
    {
        public class RegularProxy : IProxy
        {
            public ProxyResponse Process(ProxyRequest request)
            {
                var response = new ProxyResponse {RequestId = request.Id};
                try
                {
                    using (var client = new WebClient())
                    {
                        string data = client.DownloadString(request.Url);
                        response.Content = data;
                    }
                }
                catch (Exception exception)
                {
                    response.Error = exception.Message;
                }
    
                return response;
            }
        }
    }

    Don’t forget this is a very basic Proxy implementation and doesn’t implement all the features we can think of. But this first basic set up can easily be enhanced with for example Credentials support, Caching and other things as well.

    But how can we use it in our Silverlight application? Simple as well.

    var domainProxy = new DomainProxy();
    domainProxy.ProcessCompleted += (sender, e) => Debug.WriteLine((e.ReturnValue as ProxyResponse).Content);
    domainProxy.Process(new ProxyRequest
                            {
                                Id = Guid.NewGuid(), 
                                Url = "http://www.silverlight.net/"
                            });

    Looks like the keyword is simple when we’re creating RIA Services. I really like it.

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (1)

  • Silverlight 3 and RIA Services – The advanced things

    My last article was about the basics of RIA Services. This article goes about the more advanced things in RIA Services. This article contains a few things that make the usage of RIA Services in more advanced scenario’s possible.

    • Complex Types in RIA Services
    • RIA Services in a separate class library
    • RIA Services and Out of browser mode
    • RIA Services on a different host

    Using Complex Types in RIA Services

    In my other article I just made use just a string as input parameter and a string as output parameter. It’s however very well possible to use custom types instead. There’s hower one prerequisite for those custom types.

    • Each Custom Type needs to have one property decorated with a KeyAttribute. This is required for input and output types.

    I created a WorldRequest and a WorldResponse which fits nicely in the example for HelloWorld.

    public class WorldRequest
    {
        [Key]
        public string Name { get; set; }
    }
    
    public class WorldResponse
    {
        [Key]
        public string Greeting { get; set; }
    }

    This can be used in the HelloWorldDomainService just like you would do in a normal class.

    [EnableClientAccess]
    public class HelloWorldDomainService : DomainService
    {
        [ServiceOperation]
        public WorldResponse HelloWorld(WorldRequest worldRequest)
        {
            return new WorldResponse { Greeting = string.Format("Hello {0}.", worldRequest.Name) };
        }
    }

    After compilation those custom types get generated in the Silverlight project as well as we saw in my previous article. The Custom Type is now overloaded from Entity. And has a specific method called GetIdentity. This has something to do with the KeyAttribute that’s required to add to one property inside a custom types. Although this is probably necessary for integration with the Entity Framework it doesn’t make sense in my example.

    [DataContract(Namespace="http://schemas.datacontract.org/2004/07/MM.Ria")]
    public sealed partial class WorldRequest : Entity
    {
        
        private string _name;
        
        [DataMember()]
        [Key()]
        public string Name
        {
            get
            {
                return this._name;
            }
            set
            {
                if ((this._name != value))
                {
                    this.ValidateProperty("Name", value);
                    this.RaiseDataMemberChanging("Name");
                    this._name = value;
                    this.RaiseDataMemberChanged("Name");
                }
            }
        }
        
        public override object GetIdentity()
        {
            return this._name;
        }
    }
    

    We can now easily use this generated class as we would have done in different scenario’s.

    var helloWorld = new HelloWorldDomainContext();
    helloWorld.HelloWorldCompleted += HelloWorldHelloWorldCompleted;
    helloWorld.HelloWorld(new WorldRequest{Name = "Mark Monster"});

    And in the EventHandler we have to cast the ReturnValue property to the WorldResponse type. Again I hope they make InvokeEventArgs a generic type so that we don’t have to cast this ReturnValue and have it return the correct type.

    private void HelloWorldHelloWorldCompleted(object sender, InvokeEventArgs e)
    {
        Debug.WriteLine((e.ReturnValue as WorldResponse).Greeting);
    }

    Move code for the RIA Service to a class library

    Next thing that came in my mind was the fact what if I want to share my RIA Services with other people? Do they need to have the source or is a different approach possible?

    Luckily it’s possible to create class library (standard .NET) add references to a few dlls.

    image

    After that just move your RIA Service code in there. This library can now be used in any Web project that’s linked to a Silverlight application. Just add a reference to the new library containing your RIA Service and while building code will get generated in your Silverlight application.

    image

    What about using RIA Services in the Out of browser mode?

    It just works. I didn’t have to do anything to make the Out of browser version communicate with the RIA Services.

    When the host running the RIA Services is different from the url hosting the Silverlight application?

    Of course in some scenario’s the RIA Services are hosted on a different location than the Silverlight application itself. Of course you have to remind yourself about cross domain issues, so make sure the host has a clientaccesspolicy.xml or crossdomain.xml file in place.

    When we take a closer look into the generated code we’ll find a default constructor and a constructor where you can specify the service URI.

    /// <summary>
    /// Default constructor.
    /// </summary>
    public HelloWorldDomainContext() : 
            base(new HttpDomainClient(new Uri("DataService.axd/MM-Ria-HelloWorldDomainService/", System.UriKind.Relative)))
    {
    }
    
    /// <summary>
    /// Constructor used to specify a data service URI.
    /// </summary>
    /// <param name="serviceUri">
    /// The HelloWorldDomainService data service URI.
    /// </param>
    public HelloWorldDomainContext(Uri serviceUri) : 
            base(new HttpDomainClient(serviceUri))
    {
    }

    We can now make use of the constructor which accepts a serviceUri to connect to the RIA Service that’s running at a different location. The tricky part however is the DataService.axd/MM-Ria-HelloWorldDomainService/ part. Besides DataService.axd it contains also the namespace and type name for the RIA Service, but instead of using dots to separate the namespace parts and the type name dashes are used. But for the easy parts, just copy it from the generated code I would say.

    var helloWorld = new HelloWorldDomainContext(new Uri("http://myhostruningsomewhere.com/DataService.axd/MM-Ria-HelloWorldDomainService/", UriKind.Absolute));
    helloWorld.HelloWorldCompleted += HelloWorldHelloWorldCompleted;
    helloWorld.HelloWorld(new WorldRequest {Name = "Mark Monster"});
    

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (2)

  • Silverlight 3 and RIA Services – The basics

    At the Mix 09 conference Microsoft announced a new technology called RIA Services. This new technology simplifies the communication between the RIA Client Technologies like Ajax and Silverlight and the Server technology ASP.NET.

    Everybody who has been using Silverlight 2 knows about the lack of support for specific features that we’re used to make use of in for example ASP.NET. And yes, we need to remember that Silverlight is a technology that runs inside the browser (or outside the browser when activated the specific Silverlight 3 Beta feature).

    image

    Let’s start with a very simple RIA Service and let’s call it HelloWorldDomainService. I’ve seen other articles about RIA Services but they most of the time dive deep by combining the Entity Framework right away.

    First thing: what special things does a RIA Service need?

    • The RIA Service needs to be derived from DomainService, or from a different class that’s derived from DomainService.
    • The RIA Service needs to be decorated with an attribute called EnableClientAccessAttribute.
    • The operations need to be decorated with an attribute called ServiceOperationAttribute.

    A very simple HelloWorldDomainService now looks like this (this class needs to be in the ASP.NET Project, not the Silverlight project).

    [EnableClientAccess]
    public class HelloWorldDomainService : DomainService
    {
        [ServiceOperation]
        public string HelloWorld(string name)
        {
            return string.Format("Hello {0}.", name);
        }
    }

    Second thing that’s important is the configuration is correct.

    So what’s required? A configuration for a new HttpHandler specially for RIA Services in general. Just add this piece in the HttpHandlers part of the Web.config.

    <add path="DataService.axd" verb="GET,POST" type="System.Web.Ria.DataServiceFactory, System.Web.Ria, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>

    Next thing is getting the Silverlight project bound to the ASP.NET project.

    When you right click on the Silverlight project and choose properties you’ll get something similar to this.

    image

    Select the ASP.NET server project link to the ASP.NET server project that contains the RIA Services. In this example the ASP.NET server project is called MM.Sl3Experiments.Web.

    Let’s build the whole solution…

    … what happens? Let’s have a look at all files (also hidden files) in the Silverlight project by hitting the show all files button in the solution explorer. And you will suddenly see a folder called Generated_Code containing a file alike to <WebProjectName>.g.cs.

    image

    So there’s a lot of code generation behind the scenes? Yes it is. And that’s why I recommend people to write kind of a Service Agent so you’re not actually calling the generated code directly. But that’s something for a different post, or something you can figure out yourself. We’re now talking about RIA Services specifically.

    But what gets generated?

    For the sake of this article I removed a lot of code from this generated class, things that are just there but have no real purpose in this example. What we have left is this:

    public sealed partial class HelloWorldDomainContext : DomainContext
    {
        public HelloWorldDomainContext() :
            base(new HttpDomainClient(new Uri("DataService.axd/MM-Sl3Experiments-Web-HelloWorldDomainService/", System.UriKind.Relative)))
        {
        }
    
        public event System.EventHandler<InvokeEventArgs> HelloWorldCompleted;
    
        public void HelloWorld(string name)
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("name", name);
            base.InvokeServiceOperation("HelloWorld", typeof(string), parameters, this.OnHelloWorldCompleted, null);
        }
    
        private void OnHelloWorldCompleted(InvokeEventArgs args)
        {
            if ((HelloWorldCompleted != null))
            {
                this.HelloWorldCompleted(this, args);
            }
        }
    }
    

    What we have in the generated code are the following things:

    1. A constructor to create a HelloWorldDomainContext.
    2. A method that’s called HelloWorld with typed arguments.
    3. An event that get’s called upon completion of the HelloWorld execution.

    So we can easily write this code inside our Silverlight application to call the HelloWorldDomainService that’s really running on the ASP.NET Server side.

    var helloWorld = new HelloWorldDomainContext();
    helloWorld.HelloWorldCompleted += new EventHandler<InvokeEventArgs>(HelloWorldHelloWorldCompleted);
    helloWorld.HelloWorld("Mark Monster");

    Just very simple, create a new object, attach to the Completed event and call the method.

    The method being called upon completion only prints the return value to the Debug window.

    private void HelloWorldHelloWorldCompleted(object sender, InvokeEventArgs e)
    {
        Debug.WriteLine(e.ReturnValue as String);
    }
    

    Places for improvement for RIA Services…

    One thing that can be made better is the fact that InvokeEventArgs doesn’t have typed property ReturnValue. It’s of type object, and you still need to cast it to the right type. A recommendation for the team behind RIA services would be to make InvokeEventArgs generic (InvokeEventArgs<T>) to make something like this possible:

    /// <summary>
    /// The generated class
    /// </summary>
    public sealed partial class HelloWorldDomainContext : DomainContext
    {
        public HelloWorldDomainContext() :
            base(new HttpDomainClient(new Uri("DataService.axd/MM-Sl3Experiments-Web-HelloWorldDomainService/", System.UriKind.Relative)))
        {
        }
    
        public event System.EventHandler<InvokeEventArgs<string>> HelloWorldCompleted;
    
        public void HelloWorld(string name)
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("name", name);
            base.InvokeServiceOperation("HelloWorld", typeof(string), parameters, this.OnHelloWorldCompleted, null);
        }
    
        private void OnHelloWorldCompleted(InvokeEventArgs<string> args)
        {
            if ((HelloWorldCompleted != null))
            {
                this.HelloWorldCompleted(this, args);
            }
        }
    }

    And an improved way of using. The ReturnValue is now typed.

    var helloWorld = new HelloWorldDomainContext();
    helloWorld.HelloWorldCompleted += new EventHandler<InvokeEventArgs<string>>(HelloWorldHelloWorldCompleted);
    helloWorld.HelloWorld("Mark Monster");
    
    private void HelloWorldHelloWorldCompleted(object sender, InvokeEventArgs<string> e)
    {
        Debug.WriteLine(e.ReturnValue);
    }

    Another improvement would be to include the generated files in the project. This is because of two reasons. The first and most important reason would be Source Control. I think we want to have those generated files under Source Control as well. We now would have to add those files manually to source control, or right click on those generated items to include them in the project, so the Source Control Systems will pick them up automatically.

    The second reason I want those files to be included is: ReSharper, although I know it’s not a product by Microsoft. Because the generated files are not part of the project,  I won’t be able to make use of ReSharper’s intellisense and what about the ReSharper Code Analysis?

    In the end I really like this ability to run code on the server side this easily. I know RIA Services is still in CTP, let’s hope for a few improvements. It’s already a good product, but it could be even better.

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (1)

  • In memory cache for Silverlight 2 and 3

    I’ve seen some people talking about Silverlight Caching on the Silverlight forum. And because there’s no solution that can be compared to for example the ASP.NET solution for Caching. Of course it’s not that difficult to keep some data in a simple reference, just a variable, maybe on global maybe on class level. But yes, if you want a little bit more advanced Cache which supports the expiring of data you’ll need something different.

    For the general purpose of caching data which can expire after a certain timespan I wrote this solution that can be used in Silverlight 2 and 3. You can view the whole code at the end of this article.

    Adding and getting data from Cache

    I wanted a solution that could work with general objects the way I work in this example.

    Cache.Current.Add("Testkey", 10, TimeSpan.FromMinutes(2));
    object cacheData = Cache.Current.Get("Testkey");
    if(cacheData!=null)
    {
        int cacheDataTyped = (int) cacheData;
    }

    In those situations you will have to cast the data to the required Type or Structure. This works fine, we’ve done this before while accessing the ASP.NET Cache. But sometimes you’ll want to have it typed immediately. So I want to be able to use this as well.

    User user = GetUser();
    Cache.Current.Add("Testkeytyped", user, TimeSpan.FromMinutes(4));
    User userFromCache = Cache.Current.Get<User>("Testkeytyped");

    So I had to add a Generic function Get and a none Generic function Get. I did this, and the code above works fine indeed.

    But what about expiring data?

    To expire data I wanted to provide a TimeSpan while adding the item to the cache. The first solution I thought of was to remove items upon get if it’s no longer valid. But in the end I thought what if the amount of data is large, in those cases you want it removed as soon as it is expired. So I found a different solution. By making use of the Timer class in Silverlight. I want the timer to tick every minute, and on tick I want it to clean the cache. So the current solutions cleans up items in the cache every minute. And upon getting of items it will check if the item is still valid or not.

    Please feel free to use this is solution. It’s not thread-safe, but you can add thread-safe support yourself if you want to.

    /// <summary>
    /// Class supports in memory cache for Silverlight applications. Silverlight 2 and 3 are supported.
    /// Each minute the cache items are iterated for validility, invalid cache items are removed.
    /// </summary>
    public class Cache : IDisposable
    {
        public static Cache Current = new Cache();
        private readonly IDictionary<string, CacheItem> _cacheItems = new Dictionary<string, CacheItem>();
    
        private readonly TimeSpan _period = TimeSpan.FromMinutes(1);
        private readonly TimeSpan _startTimeSpan = TimeSpan.Zero;
        private readonly TimeSpan _stopTimeSpan = TimeSpan.FromMilliseconds(-1);
        private readonly Timer _timer;
        private TimerState _state = TimerState.Stopped;
    
        private Cache()
        {
            _timer = new Timer(CleanUpItems, this, _stopTimeSpan, _period);
        }
    
        /// <summary>
        /// All the CacheItems are in a Dictionary
        /// </summary>
        public IDictionary<string, CacheItem> CacheItems
        {
            get { return _cacheItems; }
        }
    
        /// <summary>
        /// Get full CacheItem based on key.
        /// </summary>
        /// <param name="key">The key for which a CacheItem is stored.</param>
        /// <returns>The CacheItem stored for the given key, if it is still valid. Otherwise null.</returns>
        public CacheItem this[string key]
        {
            get
            {
                if (CacheItems.ContainsKey(key))
                {
                    CacheItem ci = CacheItems[key];
                    if (ci.IsValid())
                        return CacheItems[key];
                }
                return null;
            }
            set { CacheItems[key] = value; }
        }
    
        #region IDisposable Members
    
        public void Dispose()
        {
            _timer.Dispose();
        }
    
        #endregion
    
        /// <summary>
        /// Get data typed directly for given key.
        /// </summary>
        /// <typeparam name="T">The Type for which the data is set. If the type is wrong null will be returned.</typeparam>
        /// <param name="key">The key for which a CacheItem is stored.</param>
        /// <returns>The data typed for the given key, if it is still valid. Otherwise null.</returns>
        public T Get<T>(string key) where T : class
        {
            CacheItem item = this[key];
            if (item != null)
                return item.GetData<T>();
            return null;
        }
    
        /// <summary>
        /// Get data untyped directly for given key.
        /// </summary>
        /// <param name="key">The key for which a CacheItem is stored.</param>
        /// <returns>The data untyped for the given key, if it is still valid. Otherwise null.</returns>
        public object Get(string key)
        {
            CacheItem item = this[key];
            if (item != null)
                return item.GetData();
            return null;
        }
    
        private void StartTimer()
        {
            if (_state == TimerState.Stopped)
            {
                _timer.Change(_startTimeSpan, _period);
                _state = TimerState.Started;
            }
        }
    
        private void StopTimer()
        {
            if (_state == TimerState.Started)
            {
                _timer.Change(_stopTimeSpan, _period);
                _state = TimerState.Stopped;
            }
        }
    
        /// <summary>
        /// Clean up items that are not longer valid.
        /// </summary>
        /// <param name="state">Expect state to be the cache object.</param>
        private static void CleanUpItems(object state)
        {
            var cache = state as Cache;
            if (cache != null)
            {
                List<KeyValuePair<string, CacheItem>> itemsToRemove =
                    cache.CacheItems.Where(i => !i.Value.IsValid()).ToList();
                foreach (var item in itemsToRemove)
                {
                    cache.CacheItems.Remove(item.Key);
                }
                if (cache.CacheItems.Count == 0)
                    cache.StopTimer();
            }
        }
    
        /// <summary>
        /// Add a new item to the cache. If the key is already used it will be overwritten. 
        /// </summary>
        /// <param name="key">The key for which a CacheItem is stored.</param>
        /// <param name="value"></param>
        public void Add(string key, CacheItem value)
        {
            if (_cacheItems.ContainsKey(key))
                _cacheItems.Remove(key);
            _cacheItems.Add(key, value);
            StartTimer();
        }
    
        /// <summary>
        /// Add a new item to the cache. If the key is already used it will be overwritten. 
        /// </summary>
        /// <param name="key">The key for which a CacheItem is stored.</param>
        /// <param name="data">The data to cache.</param>
        /// <param name="validDuration">The duration of the caching of the data.</param>
        public void Add(string key, object data, TimeSpan validDuration)
        {
            Add(key, new CacheItem(data, validDuration));
        }
    
        /// <summary>
        /// Removes the item for the given key from the cache.
        /// </summary>
        /// <param name="key">The key for which a CacheItem is stored.</param>
        /// <returns></returns>
        public bool Remove(string key)
        {
            bool remove = _cacheItems.Remove(key);
            if (_cacheItems.Count == 0)
                StopTimer();
            return remove;
        }
    
        #region Nested type: TimerState
    
        /// <summary>
        /// Used for determing TimerState
        /// </summary>
        private enum TimerState
        {
            Stopped,
            Started
        }
    
        #endregion
    }

    /// <summary>
    /// Defines an item that's stored in the Cache.
    /// </summary>
    public class CacheItem
    {
        private readonly DateTime _creationDate = DateTime.Now;
    
        private readonly TimeSpan _validDuration;
    
        /// <summary>
        /// Constructs a cache item with for the data with a validity of validDuration.
        /// </summary>
        /// <param name="data">The data for the cache.</param>
        /// <param name="validDuration">The duration for the data being valid in the cache.</param>
        public CacheItem(object data, TimeSpan validDuration)
        {
            _validDuration = validDuration;
            Data = data;
        }
    
        /// <summary>
        /// The data in the Cache.
        /// </summary>
        public object Data { set; private get; }
    
        /// <summary>
        /// Gets the Data typed.
        /// </summary>
        /// <typeparam name="T">The Type for which the data is set. If the type is wrong null will be returned.</typeparam>
        /// <returns>The data typed.</returns>
        public T GetData<T>() where T : class
        {
            return Data as T;
        }
    
        /// <summary>
        /// Gets the Data untyped.
        /// </summary>
        /// <returns>The data untyped.</returns>
        public object GetData()
        {
            return Data;
        }
    
        /// <summary>
        /// Check if the Data is still valid.
        /// </summary>
        /// <returns>Valid if the validDuration hasn't passed.</returns>
        public bool IsValid()
        {
            return _creationDate.Add(_validDuration) > DateTime.Now;
        }
    }
    

    Ps. This article is cross posted on: Mark Monster’s blog and Silverlight Help.

    Full story

    Comments (0)