Silverlight multi-binding for WPF

by StefanOlson 21. July 2009 10:42

In my previous post on Silverlight multi-binding support I mentioned that theoretically it should be possible to use the Silverlight multi-bindings in WPF. Unfortunately the practicalities of single sourcing between WPF and Silverlight are not always as simple as you might expect, and this case was no different!

Given that WPF already has its own multi-binding functionality, why would you want to use the Silverlight attack? You only really want to do this when you want to have source code compatibility between WPF and Silverlight. The updated source code for the Silverlight multi-bindings, which is available below demonstrates this with an application that uses the same source code for both WPF and Silverlight.

So onto the difficulties in making it work for WPF. The first problem is that Colin’s original structure used an ObservableCollection for the bindings:

<local:MultiBinding TargetProperty="Text" Converter="{StaticResource TitleConverter}">
    <Binding Path="Surname"/>                            
    <Binding Path="Forename"/>
</local:MultiBinding>

Unfortunately,WPF will not let you put bindings into an ObservableCollection, so I had to build a new BindingCollection class based on the model used by multi-bindings in WPF.

Once that was solved the larger problem was the fact that objects are created differently from the Xaml/Baml between Silverlight and WPF. This meant the assumptions about the timing of the creation and the completeness of an object at any given time were wrong.  WPF’s Baml reader will create an object, assign it to a dependency property and then fill its attributes. Silverlight’s Xaml reader will create an object, fill it’s attributes and then assign it to the dependency property, a feature the Colin was relying on.

So I had to come up with a completely different solution. What I decided to do was catch the loaded event of the object that we are connected to and at that time I convert the bindings that we have created in to pure WPF multi-bindings as you can see in the code below:

#if !SILVERLIGHT
void Loaded(object sender, RoutedEventArgs e)
{
    _targetElement.Loaded -= Loaded;
    foreach (MultiBinding binding in Bindings)
    {
        FieldInfo field = _targetElement.GetType().GetField(binding.TargetProperty + "Property",
                                                            BindingFlags.Public | BindingFlags.Static |
                                                            BindingFlags.FlattenHierarchy);
        if (field == null) continue;

        System.Windows.Data.MultiBinding newBinding = new System.Windows.Data.MultiBinding
                                                          {
                                                              Converter = binding.Converter,
                                                              ConverterParameter = binding.ConverterParameter
                                                          };
        foreach (BindingBase bindingBase in binding.Bindings)
        {
            newBinding.Bindings.Add(bindingBase);
        }
        
        DependencyProperty dp = (DependencyProperty)field.GetValue(_targetElement);

        BindingOperations.SetBinding(_targetElement, dp, newBinding);
    }
}
#endif

The only tricky part is finding the dependency property that we need to work with - This is a bit of a hack where I search for a static field with the given name and Property appended to it, which is the naming convention for dependency properties.

Using the multi-bindings is still exactly the same as described in the previous post but can now be used on both WPF and Silverlight!

Download Source

you can download the latest source code from here

…Stefan

Tags:

Silverlight | WPF

Improvements to Silverlight Multi-binding support

by StefanOlson 19. July 2009 14:57

Update 29/April/2010: Have changed the examples and updated the zip file to reflect to the changes required for Silverlight 4 compatibility (see here for details).

Hopefully some of you have picked up the recent blog post by Colin Eberhardt, entitled Silverlight MultiBindings, How to attached multiple bindings to a single property.  It provides an excellent basis for multi-binding support in Silverlight until they actually get on with it and become more compatible with WPF and have multi-binding directly built in!

However, when I came to use the multi-binding support, there was a limit with Colin's original design that you can only have one multi-binding per element, as you can see by this example:

<TextBlock x:Name="Block" Foreground="White" FontSize="13"
           Margin="5,0,0,0">
    <local:BindingUtil.MultiBinding>
        <local:MultiBinding TargetProperty="Text" Converter="{StaticResource TitleConverter}">
            <Binding Path="Surname"/>                            
            <Binding Path="Forename"/>
        </local:MultiBinding>
    </local:BindingUtil.MultiBinding>
</TextBlock>

If you also wanted to hide the text block if forename or surname were empty, under the original design that you couldn't do this, because you can only have one target property. So I've made some changes which allows you to have multiple multi-bindings per element.  So to make the example I'm describing possible the code would now look like this:

<TextBlock x:Name="Block" Foreground="White" FontSize="13"
            Margin="5,0,0,0">
    <local:BindingUtil.MultiBindings>
        <local:MultiBindings>
            <local:MultiBinding TargetProperty="Text" Converter="{StaticResource TitleConverter}">
                <local:BindingCollection>
                    <Binding Path="Surname"/>                            
                    <Binding Path="Forename"/>
                </local:BindingCollection>
            </local:MultiBinding>
            <local:MultiBinding TargetProperty="Visibility" Converter="{StaticResource TitleToVisibiltyConverter}">
                <local:MultiBinding.Bindings>
                    <local:BindingCollection>
                <Binding Path="Surname"/>                            
                <Binding Path="Forename"/>
                        </local:BindingCollection>
                    </local:MultiBinding.Bindings>
            </local:MultiBinding>
        </local:MultiBindings>
    </local:BindingUtil.MultiBindings>
</TextBlock>

So, now you can have an unlimited number of multi-bindings on the TextBlock!

Unfortunately, this makes a single multi-binding slightly more complicated because you need to declare the multi-bindings object to contain only a single binding. So the original example, if you were just having a single binding it would look like this:

<TextBlock x:Name="Block" Foreground="White" FontSize="13"
            Margin="5,0,0,0">
    <local:BindingUtil.MultiBindings>
        <local:MultiBindings>
            <local:MultiBinding TargetProperty="Text" Converter="{StaticResource TitleConverter}">
                <local:BindingCollection>
                    <Binding Path="Surname"/>                            
                    <Binding Path="Forename"/>
                </local:BindingCollection>
            </local:MultiBinding>
        </local:MultiBindings>
    </local:BindingUtil.MultiBindings>
</TextBlock>

Source Code

you can download the source code for the project here slmultibinding.zip

WPF Compatibility

The code here should (in theory) be compatible with WPF if you want to single source your xaml code between WPF and Silverlight.  Hopefully Silverlight 4 will have multi-binding and this workaround will no longer be required.

…Stefan

Tags:

Silverlight

Displaying a login dialog based on page attributes in Silverlight 3

by StefanOlson 19. June 2009 15:44

Update: The latest version of the code is available for download here. This page has been re-written for the .net ria Services July CTP.

One of the feature requests I described in my post Issues with the Silverlight Navigation architecture was the ability to use an attribute to declare the required roles to display a particular page.  e.g:

[RequiresAuthentication]
public partial class CategoryPage : Page

Unfortunately this is not part of Silverlight 3, so I set about implementing it myself based on the custom URI mapper I have developed in previous posts.

Let me introduce the AuthenticatingUriMapper!  You can download the source code and example here.

All source code described below is part of the sample.  It shows how you can provide attributes on a page to automatically have a login dialog displayed when navigating to it if required!  The sample is based on the Silverlight business application template, that is installed as part of the .net RIA services.  Sample requires the .net RIA services to be installed.

e.g:

image

Using it couldn't be simpler! Just like the custom URI mapper, you add it as your uriMapper (in the Frame or as a StaticResource):

<Navigation2:AuthenticatingUriMapper DefaultPage="/Views/EmptyPage.xaml">
    <Navigation2:CustomUriMapper.CustomUriMappings>
        <UriMappingNavigationSample:AboutBoxRoute/>
        <routing:CustomRoute/>
    </Navigation2:CustomUriMapper.CustomUriMappings>
</Navigation2:AuthenticatingUriMapper>

See my previous post for details on the default page attribute used above.

Then in your mainpage constructor, you tell it about the frame and handle any authentication requests:

public MainPage()
{
    InitializeComponent();
    this.loginContainer.Child = new LoginControl();
    
    (ContentFrame.UriMapper as AuthenticatingUriMapper).Frame = frame;
    (ContentFrame.UriMapper as AuthenticatingUriMapper).AuthenticationRequired += MainPage_AuthenticationRequired;
}

In the MainPage_AuthenticationRequired function, you simply display the login dialog.  We are passing the event arguments so that later on, when login has been completed, we can verify , that , the new login credentials are valid for the page the user wishes to go to.

void MainPage_AuthenticationRequired(object sender, AuthenticationRequiredEventArgs e)
{
    LoginWindow dialog = new LoginWindow { AuthenticationRequiredEventArgs = e};
    dialog.Show();
}

Then, in the pages we wish to require authentication for, add either of the following two attributes:

[RequiresAuthentication]
public partial class UserPage : Page

Or, if you need specific roles:

[RequiresRoles("Administrator")]
public partial class AdminPage : Page

In the login dialog we can make some changes to make use of the AuthenticationRequiredEventArgs.

When the login is successful , we need to verify that now that the user is logged in they passed the authentication requirements of the page they are going to . Here's how we do this:

else if (loginOp.LoginSuccess)
{
    DialogResult = true;
    if (AuthenticationRequiredEventArgs != null)
    {
        AuthorizationAttribute attribute = AuthenticationRequiredEventArgs.RecheckAttributes();
        // verify the newly logged in account meets the requirements of the page we are visiting
        if (attribute == null)
        {
            // now we know we've passed the requirements, we can navigate to the page the user 
            // originally wanted to go to
            AuthenticationRequiredEventArgs.Frame.Navigate(AuthenticationRequiredEventArgs.UnmappedUri);
_authOp=null;
DialogResult=false; return; } if (attribute is RequiresRolesAttribute && RiaContext.Current.User.IsAuthenticated) { MessageBox.Show("This account does not have authorization to view the page you are requesting.");


DialogResult = false;
_authOp=null;



return; } } }

For this  demo we're going to display a message if you are logged in using an account that doesn't have the appropriate level of credentials. For example, if you need administrator credentials to access a page but don't currently have them we wish the login dialog to indicate that.  In the picture below, we are currently logged on under the user account, but we actually need administrative privileges, so you can see that a message is displayed to that effect:

image

The code to achieve this is pretty simple, We just display the yellow box if appropriate when the event arguments are set:

private AuthenticationRequiredEventArgs _authenticationRequiredEventArgs;
public AuthenticationRequiredEventArgs AuthenticationRequiredEventArgs
{
    get { return _authenticationRequiredEventArgs; }
    set { _authenticationRequiredEventArgs = value;
        if (AuthenticationRequiredEventArgs.AuthorizationAttribute is RequiresRolesAttribute && RiaContext.Current.User.IsAuthenticated)
        {
            _RoleRequired.Visibility = Visibility.Visible;
        }
    }
}
You can try this all out yourself by downloading the source code and making use of it in your own applications!

…Stefan

Tags:

Silverlight | .net ria services

Updates to custom routing using the Uri Mapper in Silverlight 3

by StefanOlson 18. June 2009 14:14

Update: this code is now available for download, including a project file here.

In my previous post regarding custom URI routing using the URI mapper in Silverlight 3, I described a problem where if you wish to cancel the navigation, to display a dialog for example, returning null from the UriMapper can in some scenarios cause an exception.  After some discussions with Austin Lamb, a developer on the Silverlight team, it was clear that the solution I had chosen to cancel navigation was not going to work.

So I re-thought the way I was handling this and have come up with an alternative solution which works perfectly. 

To use the CustomUriMapper, add reference to the URI mapper as below, normally in the Frame in mainpage.xaml (replacing the default one):

<Navigation2:CustomUriMapper x:Key="uriMapper" DefaultPage="/Views/EmptyPage.xaml">
<Navigation:UriMapping Uri="search/{searchfor}" MappedUri="/pages/searchpage.xaml?searchfor={searchfor}"/>
<routing:CustomUriMapper.CustomUriMappings>
<routing:AboutBoxRoute/>
<
routing:CategoryRoute/>
</
routing:CustomUriMapper.CustomUriMappings>

</
Navigation2:CustomUriMapper>

Note the new default page property, this is to provide the navigation system with a page to go to, even when canceling.  If you do not need the ability to cancel page navigation, you do not need this property. To use it just create emptypage.xaml as a blank Silverlight page class. 

If you do not need the ability to cancel page navigation - in your main page constructor, you need to tell the URI mapper about the frame in which it will be displayed:

public MainPage()
{
    InitializeComponent();
    
    (ContentFrame.UriMapper as CustomUriMapper).Frame = ContentFrame;
}

The custom routes work exactly the same as described in the previous post:

internal class CategoryRoute : CustomUriMapping
{
    public override bool MapUri(Uri uri, out Uri mappedUri)
    {
        // check for a category
        CachedCategory category = ClientStaticCache.GetCategoryByPath(uri.ToString());
        mappedUri = category != null
                        ? new Uri(string.Format("/Pages/CategoryPage.xaml?categoryid={0}", category.CategoryId),
                                  UriKind.RelativeOrAbsolute)
                        : null;
        return mappedUri != null ? true : false;
    }
}

MapUri returns a bool because there are situations where you want to return a null URI. Why would you want to do this you ask, because in some scenarios you may wish to use a URI to display a dialog, for example, an about dialog.  In this case you might have a mapping that looks like this:

internal class AboutBoxRoute: CustomUriMapping
{
    public override bool MapUri(Uri unMappedUri, out Uri mappedUri)
    {
        mappedUri = null;
        if (unMappedUri.ToString()=="About")
        {
            AboutDialog dialog = new AboutDialog();
            dialog.Show();
            return true; // we are not going to go anywhere, just let the dialog display
        }
        mappedUri = null;
        return false;
    }
}

The new CustomUriMapper is shown below.  The code is be available for download hereor in my next post, which describes the new authenticating URI mapper, a subclass of the CustomUriMapper.

/// <summary>
/// Custom URI mapper provides support for custom URI mapping using code, rather than just using regular expressions, 
/// which is the only support provided by the Microsoft URI mapper.  it also provides support for canceling navigation
/// if you wish to display a dialog when a URI is specified
/// </summary>
/// <example>
/// There are two parts to using the custom URI mapper. Firstly, you declare the URI mapper and app.xaml as you would the
/// normal Microsoft mapper. If you wish to be able to cancel navigation, you can set a default page, in app.xaml. Secondly, 
/// if you wish to be able to cancel navigation to display a dialog or anything else, you need to set the frame.
/// 
/// In app.xaml:
/// <code>
/// <routing:CustomUriMapper x:Key="uriMapper" DefaultPage="/Views/EmptyPage.xaml">
//      <Navigation:UriMapping Uri="search/{searchfor}" MappedUri="/pages/searchpage.xaml?searchfor={searchfor}"/>
//  </routing:CustomUriMapper>
/// </code>
/// 
/// To set the frame, probably in the main window.
/// <code>
/// (App.Current.Resources["uriMapper"] as CustomUriMapper).Frame = frame;
/// </code>
/// 
/// </example>
[ContentProperty("UriMappings")]
public class CustomUriMapper : UriMapperBase
{
    // private variables
    private bool _cancelNavigation;

    /// <summary>
    /// Gets or sets a list of UriMapping objects.
    /// </summary>
    public Collection<UriMapping> UriMappings { get; private set; }
    /// <summary>
    /// Gets or sets a list of CustomUriMappings objects.
    /// </summary>
    public Collection<CustomUriMapping> CustomUriMappings { get; private set; }

    /// <summary>
    /// specifies the default page. This is required because even when canceling navigation, the Microsoft URI mapper
    /// needs to provide a page to map to. To use this create a blank page and reference it.
    /// </summary>
    public Uri DefaultPage { get; set; }

    private Frame _frame;
    /// <summary>
    /// specifies the frame in which navigation for this URI mapper will occur. If you want to be able to cancel 
    /// navigation, this frame needs to be set.
    /// </summary>
    /// <example>
    /// To set the frame, probably in the main window.
    /// <code>
    /// (App.Current.Resources["uriMapper"] as CustomUriMapper).Frame = frame;
    /// </code>
    /// </example>
    public Frame Frame
    {
        get { return _frame; }
        set
        {
            _frame = value;
            _frame.Navigating += FrameNavigating;
        }
    }

    public CustomUriMapper()
    {
        UriMappings = new Collection<UriMapping>();
        CustomUriMappings = new Collection<CustomUriMapping>();
    }
    
    /// <summary>
    /// Maps a given URI and returns a mapped URI.
    /// </summary>
    /// <param name="uri">Original URI value to be mapped to a new URI.</param>
    /// <returns>A URI derived from the <paramref name="uri"/> parameter.</returns>
    public override Uri MapUri(Uri uri)
    {
        _cancelNavigation = false;

        Collection<UriMapping> uriMappings = UriMappings;
        if (uriMappings == null)
        {
            throw new InvalidOperationException("MustNotHaveANullUriMappingCollection");
        }
        foreach (UriMapping mapping in uriMappings)
        {
            Uri uri2 = mapping.MapUri(uri);
            if (uri2 != null)
            {
                return CheckCanNavigateToUri(uri, uri2);
            }
        }

        foreach (CustomUriMapping mapping in CustomUriMappings)
        {
            Uri uri2;
            if (!mapping.MapUri(uri, out uri2)) continue;
            if (uri2 == null && Frame!=null)
            {
                return CancelNavigation();
            }
            return CheckCanNavigateToUri(uri, uri2);
        }
        // now nothing...
        return CheckCanNavigateToUri(uri, uri); 
    }

    /// <summary>
    /// cancels navigation to the current URI.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// thrown if there is no default page
    /// </exception>
    /// <returns>the new URI to navigate to, which will be the default page. This is required by 
    /// the Microsoft navigation system</returns>
    protected Uri CancelNavigation()
    {
        // if there is no URI to go to, we will cancel the navigation at the next possible opportunity
        _cancelNavigation = true;
        if (DefaultPage==null)
        {
            throw new InvalidOperationException("must have a default page");
        }
        // we need to return a valid URI, that points to the actual page, because the cancel is not 
        // checked until after the validity of the URI is checked
        return DefaultPage;
    }

    /// <summary>
    /// this function is called before any URI is returned. This gives you the opportunity to do any custom work, 
    /// such as authentication, prior to the URI being returned to the navigation system
    /// </summary>
    /// <param name="unmappedUri">the URI that was provided by the navigation system, before it was mapped</param>
    /// <param name="mappedUri">the URI, after mapping</param>
    /// <returns>the URI to navigate to, by default this is the same as the mapped URI</returns>
    protected virtual Uri CheckCanNavigateToUri(Uri unmappedUri, Uri mappedUri)
    {
        return mappedUri;
    }

    void FrameNavigating(object sender, NavigatingCancelEventArgs e)
    {
        e.Cancel = _cancelNavigation;
    }
}
…Stefan

Tags:

Silverlight

Issues with the Silverlight Navigation architecture

by StefanOlson 3. June 2009 15:56

In my last post, I described some of the ways I was able to use the Silverlight URI mapping architecture to provide more customized routing.

However, there are a few problems with the navigation/URI mapping architecture that I haven't been able to find ways to work around.

Setting a title

One of the major problems I've encountered is getting the title (in the browser's title bar) to be set. There is a field in the Page class called title. So you would think would be relatively simple. It appears that there's some difficulty in setting the page title, unless it's set at a particular time during the navigation. Here's how I try and set it:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    CachedCategory category = ClientStaticCache.GetCategoryById(
int.Parse(NavigationContext.QueryString["categoryid"])); Title = category.Name + " - " + "my site"; }

 

But the title never appears -  the URI Appears as the title. I started a thread on the Silverlight forum, but no one has been able to give me an answer as to when you can set the title so that it appears correctly in the title bar and the navigation history of the browser.

When you're working in an asynchronous world you often won't get the title at the time the navigation starts, there needs to be a way to set the title when the data has come down the line.

Asynchronous operation

If I want to go and just find out if a particular URI exists (e.g: a category), before navigating to it, I either have to have a cache of categories on my local Silverlight client, which is what I'm doing at the moment, or you need to go to the server and verify that that is a valid category. The problem with the URI mapping architecture is that it is synchronous, so it is not possible to go and ask the server and then come back to the URI mapper and say yes, this is where I want to go.

Null return from the URI mapper causes exception

As described in my previous post, if you return null from the URI mapper because you've handled the URI by displaying a dialog or something like that, an exception is caused. However, the exception doesn't happen if you change the URI in the address bar, only if you call it via Frame.Navigate.

Bug on connect

Reusing a page

Another scenario occurs when we have we have content on a page which is very slow to load. In my case the Virtual Tour Viewer has a page that contains floor plans. These are very slow to load, and I don't want to have to reload the floor plans as the page changes, because some pages display the floor plans and others don't.

Ideally, the Frame system would be able to come to you and say here's a page I'm asking for, would you like to give me the page. In this case, I could reuse the pages containing the floor plan and increase the speed of the application simply by handling an event on the frame.  e.g:

Page Frame_GetPageForUri(object sender, GetPageForUriEventArgs e)
{
    if (e.Uri.ToString() == "categorycontrol.xaml")
    {
        return CategoryControl;
    }
    return null;
}

private Page _CategoryControl;

private Page CategoryControl
{
    get
    {
        if (_CategoryControl == null)
        {
            _CategoryControl = new CategoryControl();
        }
        return _CategoryControl;
    }
}

The way I am looking to do this currently is to store the floor plan control outside of the page so that it doesn't get destroyed if the garbage collector tries to clean up when the page isn't displayed, but that is not very satisfactory.

Role-based pages

Another useful piece of the navigation architecture would be the ability to use an attribute to declare the required roles to display a particular page. If the user does not have that role, then a login dialog would be displayed, in the same way as is done with the ASP.net authentication system. Under Silverlight, you could do this in the same way as is done on domain service you have a requires authentication attribute and a requires role attribute:

[RequiresAuthentication]
public partial class CategoryPage : Page

WPF compatibility

The virtual tour viewer needs to work on both WPF and Silverlight. I've encountered a number of problems trying to share the code between WPF and Silverlight with regard to the navigation framework.  WPF 4 currently doesn't have the API improvements that have been made to the Silverlight navigation framework such as the URI mapper and a number of virtual functions in the page class.  This means I have had to create a whole set of classes just to create a level of compatibility, part of which is completely impossible because of the lack of the URI mapper!

It's not clear at this stage of this will be fixed by Beta 2 of the .net framework 4.

Overall the Silverlight navigation API is pretty good,  Hopefully some minor tweaks will make it a huge amount better.

Tags:

Silverlight | WPF

Custom routing using the Uri Mapper in Silverlight 3

by StefanOlson 3. June 2009 15:34

Update: see the latest version of this code here.

The new navigation architecture in Silverlight 3 is very exciting as it substantially improves the viability of Silverlight for use replacing html websites (except for the lack of a hyperlink class, which I can't understand not being included in Silverlight 2). I'm currently developing a website which will be running Silverlight 3 and have discovered some interesting issues with the URI mapping architecture as it currently stands in the beta.

The first thing I wanted to do with the URI mapping architecture was to add my own routing system where I could examine the URI and decide which page to send it to.  Out of the box the Silverlight has a Uri Mapper class that allows you to use regular expressions to convert the display URI into the URI you wish to use.  For example (in App.xaml):

<Navigation1:UriMapper x:Name="_uriMapper">
    <Navigation1:UriMapping Uri="About" MappedUri="/Views/About.xaml"/>
    <Navigation1:UriMapping Uri="About/{person}/" MappedUri="/Views/About.xaml?person={person}"/>
</Navigation1:UriMapper>

Unfortunately with the way that the URI mapping is structured at the moment, the only way to do this in code, rather than in xaml is to completely duplicate the UriMapper class and create your own specialized type of mapping. Ideally, there should be a UriMappingBase class and UriMapping should be derived from that. The classes that I have developed for this are shown below:

[ContentProperty("UriMappings")]
public class CustomUriMapper : UriMapperBase
{
    // Methods
    public CustomUriMapper()
    {
        UriMappings = new Collection<UriMapping>();
        CustomUriMappings = new Collection<CustomUriMapping>();
    }

    public override Uri MapUri(Uri uri)
    {
        Collection<UriMapping> uriMappings = UriMappings;
        if (uriMappings == null)
        {
            throw new InvalidOperationException("MustNotHaveANullUriMappingCollection");
        }
        foreach (UriMapping mapping in uriMappings)
        {
            Uri uri2 = mapping.MapUri(uri);
            if (uri2 != null)
            {
                return uri2;
            }
        }

        foreach (CustomUriMapping mapping in CustomUriMappings)
        {
            Uri uri2;
            if (mapping.MapUri(uri, out uri2))
            {
                return uri2;
            }
        }
        // now nothing...
        return uri;
    }


    // Properties
    public Collection<UriMapping> UriMappings { get; private set; }
    public Collection<CustomUriMapping> CustomUriMappings { get; private set; }
}

public abstract class CustomUriMapping
{
    public abstract bool MapUri(Uri unMappedUri, out Uri mappedUri);
}

This allows me to create my own custom routes so that if I need to do some custom analysis of the URI before I decide which page to use, this can be done, as shown below:

internal class CategoryRoute : CustomUriMapping
{
    public override bool MapUri(Uri uri, out Uri mappedUri)
    {
        // check for a category
        CachedCategory category = ClientStaticCache.GetCategoryByPath(uri.ToString());
        mappedUri = category != null
                        ? new Uri(string.Format("/Pages/CategoryPage.xaml?categoryid={0}", category.CategoryId),
                                  UriKind.RelativeOrAbsolute)
                        : null;
        return mappedUri != null ? true : false;
    }
}

MapUri returns a bool because there are situations where you want to return a null URI. Why would you want to do this you ask, because in some scenarios you may wish to use a URI to display a dialog, for example, an about dialog.  In this case you might have a mapping that looks like this:

internal class AboutBoxRoute: CustomUriMapping
{
    public override bool MapUri(Uri unMappedUri, out Uri mappedUri)
    {
        mappedUri = null;
        if (unMappedUri.ToString()=="About")
        {
            AboutDialog dialog = new AboutDialog();
            dialog.Show();
            return true; // we are not going to go anywhere, just let the dialog display
        }
        mappedUri = null;
        return false;
    }
}

If the unmapped URI was equal to “About”, the about dialog would display and null would be returned from the UriMapper, so no navigation would occur . Unfortunately there is a bug in the Silverlight beta, which means that returning null from the UriMapper can in some scenarios cause an exception. I’ve filed a bug on connect for this and hopefully it will be fixed before RTW.

Adding the custom URI mapper to your project is the same as adding the Microsoft one, except that you reference a different class.

<routing:CustomUriMapper x:Key="uriMapper">
    <Navigation:UriMapping Uri="search/{searchfor}" MappedUri="/pages/searchpage.xaml?searchfor={searchfor}"/>
<routing:CustomUriMapper.CustomUriMappings> <routing:AboutBoxRoute/> <routing:CategoryRoute/> </<routing:CustomUriMapper.CustomUriMappings> </routing:CustomUriMapper>

As you can see, you can add any custom routes directly in the xaml.

So there are some solutions for some issues with the URI mapping as currently implemented in Silverlight 3. In my next post I will explain some of the issues that I haven't been able to work around.

Tags:

Silverlight

Auto expanding tree view for Silverlight

by StefanOlson 14. May 2009 12:30

Bea Stollnitz has been running a series on her blog, describing how to expand items in the tree view, in both WPF and Silverlight, but much of it was extraordinarily complicated.   For the project I'm working on with Silverlight and the .net RIA services I needed one of my tree views to appear completely expanded initially.  There's a lot of code on Bea’s blog, but I cut down what I needed to this new tree view control.  You can use it in your application and your tree will initially be completely expanded:

public class AutoExpandingTreeViewItem : TreeViewItem
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new AutoExpandingTreeViewItem { IsExpanded = true };
    }
}
public class AutoExpandingTreeView : TreeView
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new AutoExpandingTreeViewItem { IsExpanded = true };
    }
}

Here's how you would use it in your page:

<UserControl x:Class="HireMyStuffAdmin.SilverlightControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:toolkit="clr-namespace:System.Windows;assembly=System.Windows.Controls" 
    xmlns:controls="clr-namespace:OlsonSoftware.controls" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <controls:AutoExpandingTreeView ItemsSource="{Binding Categories}">
            <controls:AutoExpandingTreeView.ItemTemplate>
                <toolkit:HierarchicalDataTemplate  ItemsSource="{Binding ChildCategories}">
                    <StackPanel >
                        <TextBlock Text="{Binding Name}"></TextBlock>
                    </StackPanel>
                </toolkit:HierarchicalDataTemplate>
            </controls:AutoExpandingTreeView.ItemTemplate>
        </controls:AutoExpandingTreeView>
    </Grid>
</UserControl>

Tags:

Silverlight

Login with e-mail address using .net RIA services

by StefanOlson 14. May 2009 09:16

I'm currently working a very interesting project using Silverlight 3 and the new .net RIA services. Basically we're building the whole e-commerce type website using Silverlight 3. If you don't have Silverlight 3 installed you'll be presented with HTML versions of the pages that you can browse and if you need to login or use any of the interactive features then we will require you to install Silverlight 3.

I still have concerns about the install base of Silverlight. The site is entirely focused on New Zealand users According to riastats.com, 62.5% of New Zealanders do not have Silverlight installed. This differs from the statistics provided by some developers who work for ACP media, where they said around 80% don't have Silverlight installed (ACP Media Development Leads To Pure Direction).  Either way, there is still a lot of people who do not have Silverlight installed, although it is reducing over time. This won't be a long-term problem, but is a major challenge for Microsoft when it comes to websites adopting Silverlight.

The website makes extensive use of the new .net RIA services, which is currently in preview. If you haven't heard about this, it provides a way to access your database information from a Silverlight application. For a good overview see Brad Abrams blog, here.

For a preview release, the .net RIA services seem incredibly stable. I haven't encountered any serious bugs, at this stage.

Because many users quickly forget the username for a specific site I want people to be able to login using their e-mail address. I recently started a thread on the Silverlight.net RIA services forum to try and find out if this was possible.  What I wanted to be able to do was to override the login function on the authentication service and replace the given username, which was an e-mail address with the user's actual username. The code would've looked something like this:

public class AuthenticationWithEmailLogin<T> : AuthenticationBase<T> where T : UserBase, new()
{
    public override User Login(string userName, string password, bool isPersistent)
    {
        string newUserName = Membership.GetUserNameByEmail(userName);
        if (newUserName != null) // if the user has provided an e-mail address use the username associated with that
        {
            userName = newUserName;
        }
        return base.Login(userName, password, isPersistent);
    }
} 

Unfortunately, Login is not virtual, so I can't use that option.

Eventually, Kyle suggested that I override several other functions in order to make it do what I want.  Here's what I ended up with ( simplified after some further suggestions from Kyle):

public class AuthenticationWithEmailLogin<T> : AuthenticationBase<T> where T : UserBase, new()
{
    protected virtual string GetUserNameToUse(string userName)
    {
        string newUserName = Membership.GetUserNameByEmail(userName);
        if (newUserName != null)
        {
            userName = newUserName;
        }
        return userName;
    }

    protected virtual IPrincipal GetPrincipalWithCorrectName(IPrincipal principal)
    {
        return new GenericPrincipal(
                new GenericIdentity(GetUserNameToUse(principal.Identity.Name), principal.Identity.AuthenticationType),
                new string[0]);
    }

    protected override bool ValidateUser(string userName, string password)
    {
        return base.ValidateUser(GetUserNameToUse(userName), password);
    }

    protected override T GetAuthenticatedUser(IPrincipal principal)
    {
        return base.GetAuthenticatedUser(GetPrincipalWithCorrectName(principal));
    }

    protected override void IssueAuthenticationToken(IPrincipal principal, bool isPersistent)
    {
        base.IssueAuthenticationToken(GetPrincipalWithCorrectName(principal), isPersistent);
    }
}

You can download my complete class from here

Hopefully it’ll help someone.

…Stefan

Tags:

Silverlight | .net ria services

Bugs fixed (or not) in Silverlight 3

by stefanolson 19. March 2009 16:19

I thought my first test with Silverlight 3 should be to see which bugs that I reported during the release candidate of Silverlight 2 have been fixed.  In this post I’m only dealing with things I consider bugs as opposed to feature requests. Interestingly none of my feature requests have been implemented so far even though some of them are as simple as changing properties to public.

I'm very impressed to see that so many of the bugs have been fixed, although there is still a large number still not fixed and I would assume that is relatively unlikely they will be fixed between now and the final release.

Unfortunately the Silverlight developers are not very good at updating the connect site. I would have assumed that all of the bugs that are fixed would have been closed, but none of them have been.  The only issue that has been closed is one that I've been told has been passed on to the Silverlight developers (#372170), but now that it is closed how am I going to know it's been fixed?

The bugs are listed below with the connect bug numbers and a link to the bug if you wish to examine it in more detail.

Fixed:

#372104 DataContext = this On Silverlight rc0 causes Internet Explorer to crash

#372312 Converter not called when data value changes

#372957 Should be possible to bind to a collection item in Silverlight

#373118 Tooltip should inherit DataContext of its containing element

#373684 Changing an object when you are bound to a sub item of that object fails in Silverlight

#373909 More Silverlight Data binding woes

#377446 Adding a DependencyObject to a ListBox in Silverlight causes exception

Not Fixed:

#372170 ToggleButton IsChecked should be TwoWay by default

#373116 ToolTip causes ArgumentException

#373957 Value does not fall within the expected range assigning PointCollection in Silverlight

#377171 Using a path style from app.xaml fails the second time in Silverlight

#377494 ToggleButton doesn't display the IsChecked visual state correctly

#378419 Assigning new content to a ContentControl clears other controls in the template in Silverlight

#425334 Binding to ActualWidth ignores changes in Silverlight (bug just added, but it was in SL 2.0)

Tags:

Silverlight

Silverlight 3 Beta available

by stefanolson 18. March 2009 07:07

If you've been hiding under a rock today, you won't be aware that Silverlight 3 beta is now available for download.

You can download it here:

Silverlight 3 Tools for Visual Studio

I'll be posting a more detailed review of how it improves WPF compatibility in the very near future.  In the meantime, if you want to know what's new checkout this excellent post by Tim Hauer:

http://timheuer.com/blog/archive/2009/03/18/silverlight-3-whats-new-a-guide.aspx

…Stefan

Tags:

Silverlight

About the author

Stefan Olson is the Managing Director of Olson Software.  He has been developing software using Microsoft Technologies for nearly 20 years.

He is currently working on building the next generation Virtual Tour software in WPF and Silverlight for www.palacevirtualtours.com.

Tag cloud